Android自定义控件第一发(状态提示图表)

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

1 背景

前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件。

这个实例很适合新手入门自定义控件。先看下效果图:

横屏模式如下:

竖屏模式如下:

看见没有,这个控件完全自定义的,连文字等都是自定义的,这个界面只有一个控件。如下咱们看下实现代码。

!!!!!!! 下载Demo工程源码点击我

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

2 实例代码

如下就是整个工程的源码了。

自定义上面展示的控件AreaChartsView源码:

/**
 * Author       : yanbo
 * Date         : 2015-06-03
 * Time         : 09:22
 * Description  : 自定义区域描述图表View
 */
public class AreaChartsView extends View {
    private Paint mPaint;

    private int[] mZeroPos = new int[2];
    private int[] mMaxYPos = new int[2];
    private int[] mMaxXPos = new int[2];

    private int mWidth, mHight;
    private int mRealWidth, mRealHight;
    private String mTitleY, mTitleX;

    private ArrayList<Integer> mXLevel = new ArrayList<>();
    private ArrayList<Integer> mYLevel = new ArrayList<>();
    private ArrayList<String> mGridLevelText = new ArrayList<>();
    private ArrayList<Integer> mGridColorLevel = new ArrayList<>();
    private ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();

    private int mGridLevel = mXLevel.size() - 1;

    //title字符大小
    private int mXYTitleTextSize = 40;

    private int mMeasureXpos, mMeasureYpos;

    public AreaChartsView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHight = getHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        initPosition();
        drawXYTitle(canvas);
        drawXYLine(canvas);
        drawContent(canvas);
    }

    private void initPosition() {
        //初始化坐标图的xy交点原点坐标
        mZeroPos[0] = mXYTitleTextSize * 2;
        mZeroPos[1] = mHight - mXYTitleTextSize * 4;
        //初始化坐标图的X轴最大值坐标
        mMaxXPos[0] = mWidth;
        mMaxXPos[1] = mHight - mXYTitleTextSize * 4;
        //初始化坐标图的Y轴最大值坐标
        mMaxYPos[0] = mXYTitleTextSize * 2;
        mMaxYPos[1] = mXYTitleTextSize * 2;
    }

    private void drawXYTitle(Canvas canvas) {
        mPaint.setColor(Color.parseColor("#1FB0E7"));
        mPaint.setTextSize(mXYTitleTextSize);
        mPaint.setTextAlign(Paint.Align.LEFT);
        //画Y轴顶的title
        canvas.drawText(mTitleY, mMaxYPos[0] - mXYTitleTextSize * 2, mMaxYPos[1] - mXYTitleTextSize, mPaint);
        mPaint.setTextAlign(Paint.Align.RIGHT);
        //画X轴顶的title
        canvas.drawText(mTitleX, mMaxXPos[0], mMaxXPos[1] + mXYTitleTextSize * 2, mPaint);
    }

    private void drawXYLine(Canvas canvas) {
        mPaint.setColor(Color.DKGRAY);
        mPaint.setTextAlign(Paint.Align.RIGHT);
        //画XY轴
        canvas.drawLine(mMaxYPos[0], mMaxYPos[1], mZeroPos[0], mZeroPos[1], mPaint);
        canvas.drawLine(mZeroPos[0], mZeroPos[1], mMaxXPos[0], mMaxXPos[1], mPaint);
    }

    private void drawContent(Canvas canvas) {
        mGridLevel = mXLevel.size() - 1;
        //计算出偏移title等显示尺标后的真实XY轴长度,便于接下来等分
        mRealWidth = (mWidth - mXYTitleTextSize * 2);
        mRealHight = (mHight - mXYTitleTextSize * 4);
        //算出等分间距
        int offsetX = mRealWidth/(mGridLevel);
        int offsetY = mRealHight/(mGridLevel+1);
        //循环绘制content
        for (int index=0; index<mGridLevel+1; index++) {
            mPaint.setColor(Color.DKGRAY);
            mPaint.setTextAlign(Paint.Align.RIGHT);
            mPaint.setTextSize(mXYTitleTextSize-5);
            //绘制X轴的那些坐标区间点,包含0点坐标
            canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[0]+(index*offsetX), mZeroPos[1] + mXYTitleTextSize, mPaint);

            if (index != 0) {
                //绘制Y轴坐标区间点,不包含0点坐标,X轴已经画过了
                canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[0], mZeroPos[1]-(index*offsetY), mPaint);
            }

            if (index == mGridLevel) {
                //坐标区间 = 真实区间 + 1
                break;
            }

            mPaint.setColor(mGridColorLevel.get(mGridLevel - 1 - index));
            mPaint.setStyle(Paint.Style.FILL);
            //绘制区间叠加图谱方块,从远到0坐标,因为小的图会覆盖大的图
            canvas.drawRect(mMaxYPos[0], mMaxYPos[1] + index*offsetY, mMaxXPos[0]-index*offsetX, mMaxXPos[1], mPaint);

            mPaint.setColor(mGridTxtColorLevel.get(index));
            mPaint.setTextAlign(Paint.Align.RIGHT);
            mPaint.setTextSize(mXYTitleTextSize);
            //绘制每个方块状态区间的提示文字
            canvas.drawText(mGridLevelText.get(index), mMaxXPos[0] - index * offsetX - mXYTitleTextSize,
                    mMaxYPos[1] + index * offsetY + mXYTitleTextSize, mPaint);
        }
        //绘制当前坐标
        drawNotice(canvas, offsetX, offsetY);
    }

    private void drawNotice(Canvas canvas, int offsetX, int offsetY) {
        int realPosX = 0;
        int realPosY = 0;
        //计算传入的x值与真实屏幕坐标的像素值的百分比差值转换
        for (int index=0; index<mGridLevel; index++) {
            if (mMeasureXpos >= mXLevel.get(index) && mMeasureXpos < mXLevel.get(index+1)) {
                int subValue = mMeasureXpos - mXLevel.get(index);
                int offset = mXLevel.get(index+1) - mXLevel.get(index);
                realPosX = mZeroPos[0] + index*offsetX + (subValue / offset);
                break;
            }
        }
        //计算传入的y值与真实屏幕坐标的像素值的百分比差值转换
        for (int index=0; index<mGridLevel; index++) {
            if (mMeasureYpos >= mYLevel.get(index) && mMeasureYpos < mYLevel.get(index+1)) {
                int subValue = mMeasureYpos - mYLevel.get(index);
                int offset = mYLevel.get(index+1) - mYLevel.get(index);
                realPosY = mZeroPos[1] - index*offsetY - (offsetY - (subValue / offset));
                break;
            }
        }
        //画我们传入的坐标点的标记小红点
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(realPosX, realPosY, 8, mPaint);

        int[] centerPos = {mZeroPos[0] + mRealWidth/2, mZeroPos[1] - mRealHight/2};

        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        RectF rectF = null;
        Path path = new Path();
        //画红点旁边的提示框和文字,有四个区域,然后提示框的小三角指标方位不同
        if (realPosX <= centerPos[0] && realPosY >= centerPos[1]) {
            //left-bottom
            //画三角形
            path.moveTo(realPosX+5, realPosY+5);
            path.lineTo(realPosX+15, realPosY+15);
            path.lineTo(realPosX+15, realPosY-15);
            //画矩形背景
            rectF = new RectF(realPosX+15, realPosY-40, realPosX+200, realPosY + 30);
            canvas.drawRoundRect(rectF, 15, 15, mPaint);
            //画提示框的文字
            mPaint.reset();
            mPaint.setColor(Color.RED);
            mPaint.setTextSize(mXYTitleTextSize - 5);
            canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY, mPaint);
        }
        else if (realPosX <= centerPos[0] && realPosY < centerPos[1]) {
            //left-top
            path.moveTo(realPosX+5, realPosY+5);
            path.lineTo(realPosX+15, realPosY+15);
            path.lineTo(realPosX + 15, realPosY - 15);

            rectF = new RectF(realPosX+15, realPosY - 20, realPosX+200, realPosY + 50);
            canvas.drawRoundRect(rectF, 15, 15, mPaint);

            mPaint.reset();
            mPaint.setColor(Color.RED);
            mPaint.setTextSize(mXYTitleTextSize - 5);
            canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY+20, mPaint);
        }
        else if (realPosX > centerPos[0] && realPosY >= centerPos[1]) {
            //right-bottom
            path.moveTo(realPosX-5, realPosY+5);
            path.lineTo(realPosX-15, realPosY+15);
            path.lineTo(realPosX - 15, realPosY - 15);

            rectF = new RectF(realPosX-200, realPosY-40, realPosX-15, realPosY + 30);
            canvas.drawRoundRect(rectF, 15, 15, mPaint);

            mPaint.reset();
            mPaint.setColor(Color.RED);
            mPaint.setTextSize(mXYTitleTextSize - 5);
            canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY, mPaint);
        }
        else if (realPosX > centerPos[0] && realPosY < centerPos[1]) {
            //right-top
            path.moveTo(realPosX-5, realPosY+5);
            path.lineTo(realPosX-15, realPosY+15);
            path.lineTo(realPosX - 15, realPosY - 15);

            rectF = new RectF(realPosX-200, realPosY - 20, realPosX-15, realPosY + 50);
            canvas.drawRoundRect(rectF, 15, 15, mPaint);

            mPaint.reset();
            mPaint.setColor(Color.RED);
            mPaint.setTextSize(mXYTitleTextSize - 5);
            canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY+30, mPaint);
        }

        path.close();
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(path, mPaint);
    }

    //设置当前比值
    public void updateValues(int x, int y) {
        mMeasureXpos = x;
        mMeasureYpos = y;

        postInvalidate();
    }

    //设置XY轴顶角的title字体大小
    public void setTitleTextSize(int size) {
        mXYTitleTextSize = size;
    }

    //初始化X轴的坐标区间点值,可以不均等分
    public void initXLevelOffset(ArrayList<Integer> list) {
        mXLevel.clear();
        mXLevel.addAll(list);
    }

    //初始化Y轴的坐标区间点值,可以不均等分
    public void initYLevelOffset(ArrayList<Integer> list) {
        mYLevel.clear();
        mYLevel.addAll(list);
    }

    //初始化每个区间的提示文字,如果不想显示可以设置""
    public void initGridLevelText(ArrayList<String> list) {
        mGridLevelText.clear();
        mGridLevelText.addAll(list);
    }

    //初始化每个区间的颜色
    public void initGridColorLevel(ArrayList<Integer> list) {
        mGridColorLevel.clear();
        mGridColorLevel.addAll(list);
    }

    //初始化每个区间的提示文字颜色
    public void initGridTxtColorLevel(ArrayList<Integer> list) {
        mGridTxtColorLevel.clear();
        mGridTxtColorLevel.addAll(list);
    }

    //初始化XY轴title
    public void initTitleXY(String x, String y) {
        mTitleX = x;
        mTitleY = y;
    }
}

再来看下布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.yanbober.customerviewdemo.areachartsview.AreaChartsView
        android:id="@+id/area_charts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"/>

</RelativeLayout>

再看看主界面:

public class MainActivity extends AppCompatActivity {
    private AreaChartsView mAreaChartsView;
    private Timer timer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAreaChartsView = (AreaChartsView) this.findViewById(R.id.area_charts_view);

        //初始化自定义图表的规格和属性
        ArrayList<Integer> mXLevel = new ArrayList<>();
        ArrayList<Integer> mYLevel = new ArrayList<>();
        ArrayList<String> mGridLevelText = new ArrayList<>();
        ArrayList<Integer> mGridColorLevel = new ArrayList<>();
        ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();
        //初始化x轴坐标区间
        mXLevel.add(0);
        mXLevel.add(60);
        mXLevel.add(90);
        mXLevel.add(100);
        mXLevel.add(110);
        mXLevel.add(120);
        //初始化y轴坐标区间
        mYLevel.add(0);
        mYLevel.add(90);
        mYLevel.add(140);
        mYLevel.add(160);
        mYLevel.add(180);
        mYLevel.add(200);
        //初始化区间颜色
        mGridColorLevel.add(Color.parseColor("#1FB0E7"));
        mGridColorLevel.add(Color.parseColor("#4FC7F4"));
        mGridColorLevel.add(Color.parseColor("#4FDDF2"));
        mGridColorLevel.add(Color.parseColor("#90E9F4"));
        mGridColorLevel.add(Color.parseColor("#B2F6F1"));
        //初始化区间文字提示颜色
        mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
        mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
        mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
        mGridTxtColorLevel.add(Color.WHITE);
        mGridTxtColorLevel.add(Color.BLACK);
        //初始化区间文字
        mGridLevelText.add("异常");
        mGridLevelText.add("过高");
        mGridLevelText.add("偏高");
        mGridLevelText.add("正常");
        mGridLevelText.add("偏低");

        mAreaChartsView.initGridColorLevel(mGridColorLevel);
        mAreaChartsView.initGridLevelText(mGridLevelText);
        mAreaChartsView.initGridTxtColorLevel(mGridTxtColorLevel);
        mAreaChartsView.initXLevelOffset(mXLevel);
        mAreaChartsView.initYLevelOffset(mYLevel);
        mAreaChartsView.initTitleXY("投入量(H)", "产出量(H)");
    }

    @Override
    protected void onStart() {
        super.onStart();
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Random random = new Random();
                int x = random.nextInt(120) % (120 + 1) + 0;
                Random randomy = new Random();
                int y = randomy.nextInt(200) % (200 + 1) + 0;
                //随机模拟赋值
                mAreaChartsView.updateValues(x, y);
            }
        }, 0, 1000);
    }

    @Override
    protected void onPause() {
        super.onPause();
        timer.cancel();
    }
}

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

3 总结

上面代码很简单,核心的都已经注释了,不需要过多解释。核心思路就是一些坐标点的计算。该控件支持设置mergin及width与hight等属性,支持自定义所有颜色及显示及坐标区分等,唯一缺陷就是没来得及写attr属性xml设置这些值,有兴趣的自己实现吧,我是没时间了。

可以发现,自定义View无非就是重写前面文章分析的那三个方法而已。

!!!!!!! 下载Demo工程源码点击我

重点只提供实现思路,具体细节没时间优化。有需求的可以在下面讨论。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

时间: 2024-10-01 22:23:59

Android自定义控件第一发(状态提示图表)的相关文章

Android自定义控件(状态提示图表) (转)

源:Android自定义控件(状态提示图表) 源:Android应用开发 [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1  背景 前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件. 这个实例很适合新手入门自定义控件.先看下效果图: 横屏模式如下: 竖屏模式如下: 看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的java文

Android自己定义控件(状态提示图表)

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重分享成果] 1 背景 前面分析那么多系统源代码了.也该暂停下来歇息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源代码后的实例演练--自己定义控件. 这个实例非常适合新手入门自己定义控件.先看下效果图: 横屏模式例如以下: 竖屏模式例如以下: 看见没有.这个控件全然自己定义的,连文字等都是自己定义的,没有不论什么图片等资源,就仅仅是一个小的java文件,这个界面仅仅有一个控件.例如以下咱们看下实现代

Android自定义控件背景及其Drawable以实现扁平化

扁平化? 人们都说扁平化是从IOS和WindowsPhone那边吹过来的邪风,但是不可否认:扁平化是我见过的最舒服.最自然的表现方式.从开发角度上来讲,扁平化的设计可以使得我们从许多屏幕适配和尺寸调节的工作中解放出来(虽然只是那么一点点),更加关注功能:而在在使用层面上,只要文化水平不是特别地低(没有恶意),拟物化的那点提示作用也不是那么明显,当然这里不是说拟物化不好,总之:相对于其他表现方式,扁平化碉堡了. 咱们也做一个扁平化 上面说了,扁平化的控件其实在开发中是非常容易的.这里让我们一起动手

Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件

这个例子是比较有用的,基本上可以说,写完这一次,以后很多情况下,直接拿过来addView一下,然后再addInterceptorView一下,就可以轻轻松松的达到组合界面中特定控件来响应特定方向的触摸事件了. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45198549,非允许请勿用于商业或盈利用途,违者必究. 在写Android应用的过程之中,经常会遇到这样的情况:界面包含了多个控件,我们希望触摸在界面上的不

Android自定义控件之滑动开关

自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Android原生的控件有什么相似的地方,而且在滑动的效果上也没有沿袭Android原生的地方,所以我们的自定义

Android自定义控件 -Canvas绘制折线图(实现动态报表效果)

有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了很多插件,但是很多时候我们需要根据具体项目自定义这些图表,这一篇文章我们一起来看看如何在Android中使用Canvas绘制折线图.先看看绘制的效果: 代码: public class MyView extends View { //坐标轴原点的位置 private int xPoint=60; private int yPoint=260; //刻度长度 private int xScale=8;  //8个单位构

Android自定义控件——开源组件SlidingMenu的项目集成

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39611355  在实际项目开发中,定制一个菜单,能让用户得到更好的用户体验,诚然菜单的样式各种各样,但是有一种菜单--滑动菜单,是被众多应用广泛使用的.关于这种滑动菜单的实现,我在前面的博文中也介绍了如何自定义去实现,请参考Android自定义控件--侧滑菜单,这篇博文描述的是如何从无到有创建一个侧滑菜单的控件,里面的代码不多,但是处理的逻辑和各种效果比较复杂,如果稍有不慎,这种

[转]Android自定义控件系列五:自定义绚丽水波纹效果

出处:http://www.2cto.com/kf/201411/353169.html 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二:自定义开关按钮(一)).观察这个效果,发现应该

android 等级信号状态标识View绘制

1.前言 等级信号状态的View在现在的Android系统中非常的常见,比如手机右上角的电池状态的图标就非常的经典,有几种状态,到了快没电的时候有些还会闪烁提示用户充电:还有的就是一些地图App的GPS信号强度的提示,Wifi信号强度的也有一些,反正应用场景大概就是这样. 2.实现目标效果图 废话别说这么多,直接上干货看图说话 我这个实现的是4种状态的View 1.没有中间显示条的表示空状态 2.有一个红色小圆信号低的状态 3.有一个黄色椭圆的表示一般状态 4.充满绿条的表示良好的状态 在这个V