Android自定义控件开发笔记<一>

原创 2017年01月03日 17:24:41

继承View的自定义控件—-自定义折线图

一.引言

前几天看了一篇文章,上面介绍说:Android自定义控件为基础技能。看到这个,我心里拔凉拔凉的,虽然知道自定义控件的步骤,但实际上并没有做个一个像样的自定义空间,惊恐之余,赶紧补一补,学习下Android自定义控件开发。

二.学习方式

看别人的开源代码,这里我看的是一个自定义柱状图的DEMO:
GitHub项目地址

三.开始自定义折线图

1.先上效果图

这里写图片描述
2.实现步骤:
1):先自定义属性,我这里先定义几个颜色 折线颜色,坐标值颜色,区域线颜色,在styles.xml中定义了如下属性:

<declare-styleable name="MyChartView">        
        <attr name="lineColor" format="color"></attr>
        <attr name="bodyColor" format="color"></attr>
        <attr name="textColor" format="color"></attr>
        <attr name="x_axis" format="integer"></attr>
        <attr name="y_axis" format="integer"></attr>            
    </declare-styleable>

2)开始绘图,建一个类继承View ,这个图标最关键是onDraw方法中的cavans绘制功能,不废话,直接上代码,步骤看注释:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.hankkin.mycartdemo.R;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * Created by hyz84 on 16/12/29.
 */

public class BrokenlineView extends View {
    public static final String TAG = "BrokenlineView";
    private int lineColor;
    private int bodyColor;
    private int textColor;
    private Paint linePaint, bodyPaint, textPaint;
    private List<Float> points = new ArrayList<Float>();
    private int mHeight, mWidth, mSize;

    //注意下两个构造函数的实现方法,是this,不是super
    public BrokenlineView(Context context) {
        this(context, null);
    }

    public BrokenlineView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    //注意构造函数的实现方法,是super!
    public BrokenlineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //从xml文件中获取样式属性值
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyChartView_lineColor:
                    // 默认颜色设置为红色
                    lineColor = array.getColor(attr, Color.RED);
                    break;
                case R.styleable.MyChartView_bodyColor:
                    // 默认颜色设置为灰色
                    bodyColor = array.getColor(attr, Color.GRAY);
                case R.styleable.MyChartView_textColor:
                    // 默认颜色设置为红色
                    textColor = array.getColor(attr, Color.RED);
                    break;
                default:
                    break;
            }
        }
        array.recycle();
        init();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置X轴的间隔,根据传递进来的坐标点来决定,-5为调整值,以免折线画X轴终点
        mSize = mWidth / (points.size() == 0 ? 1 : (points.size() - 1)) - 5;
        //这里是对称坐标,所以定义X轴的位置,为高度的一半
        int x_axis = mHeight / 2;
        //折线的画笔
        linePaint.setColor(lineColor);
        linePaint.setStrokeWidth(5f);
        linePaint.setStyle(Paint.Style.FILL);
        //区域线的画笔
        bodyPaint.setColor(bodyColor);
        bodyPaint.setStrokeWidth(2f);
        bodyPaint.setStyle(Paint.Style.FILL);
        //坐标值画笔
        textPaint.setColor(textColor);
        textPaint.setTextSize(20f);
        textPaint.setStyle(Paint.Style.FILL);

        int max_y_axis = getMaxAxis();
        float ratio = getRatio();
        //默认设置X轴上下为4个间隔
        int index = 5;
        for (int x = 1; x < index; x++) {
            //画X轴上面的区域线 和 区域坐标轴值
            canvas.drawLine(0f, x * x_axis / index, mWidth, x * x_axis / index, bodyPaint);
            canvas.drawText((int) (x * x_axis / (index * ratio)) + "", 5f, (index - x) * x_axis / index - 8f, textPaint);
            //画X轴下面的区域线 和 区域坐标轴值
            canvas.drawLine(0f, mHeight - x * x_axis / index, mWidth, mHeight - x * mHeight / 2 / index, bodyPaint);
            canvas.drawText(-(int) ((index - x) * x_axis / (index * ratio)) + "", 5f, mHeight - x * x_axis / index - 8f, textPaint);
        }
        //画X轴
        bodyPaint.setColor(Color.BLACK);
        canvas.drawLine(0f, x_axis, mWidth, x_axis, bodyPaint);

       //画折线
        for (int x = 0; x < points.size(); x++) {
            Log.e(TAG, "points" + points.get(x));
            //将坐标全部还原成正值
            float point = points.get(x) + max_y_axis;
            //绘制坐标点
            canvas.drawCircle((float) x * mSize, mHeight - ratio * point, 4f, linePaint);
            if (x < points.size() - 1)
                //画折线
                canvas.drawLine((float) x * mSize, (mHeight - ratio * point), (float) (x + 1) * mSize, (mHeight - ratio * (points.get(x + 1) + max_y_axis)), linePaint);


        }


    }

    /**
     * 对称坐标轴,获取Y轴坐标的最大值
     *
     * @return
     */
    private int getMaxAxis() {

        Object[] floats = points.toArray();
        Arrays.sort(floats);
        //取坐标点中绝对值最大的数
        float max = (float) floats[floats.length - 1] > Math.abs((float) floats[0]) ? (float) floats[floats.length - 1] : Math.abs((float) floats[0]);
        Log.e(TAG, "max: " + max);
        //平分坐标轴,取得Y轴的最大值
        String value  = String.valueOf(max);
        String[] values =  value.split("\\.");
        String len ="1";
        for(int x=0;x<values[0].length()-1;x++){
            len+="0";
        }
        double max_y_axis = Math.ceil(max / Integer.parseInt(len)) * Integer.parseInt(len);

        Log.e(TAG, "max_y_axis: " + max_y_axis);
        return (int) max_y_axis;
    }

    /**
     * 获取实际坐标值放到图标中的缩放比例
     *
     * @return
     */
    private float getRatio() {
        int zero_x = mHeight / 2;
        double ratio = ((double) zero_x) / getMaxAxis();
        Log.e(TAG, "ratio: " + ratio);
        return (float) ratio;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }

        setMeasuredDimension(width, height);
        Log.e(TAG, "width:" + width + "   height:" + height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    //获取自身的宽高
        mWidth = getWidth();
        mHeight = getHeight();
        Log.e(TAG, "mHeight:" + mHeight + " mWidth:" + mWidth);
        // mSize = mWidth / (13);

    }


    //初始化画笔
    private void init() {
        linePaint = new Paint();
        linePaint.setAntiAlias(true);

        bodyPaint = new Paint();
        bodyPaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);

    }

    /**
     * 设置坐标点
     * @param _points
     */
    public void setPoints(List<Float> _points) {
        points = _points;
    }
}

再上布局文件代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myChart="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_six"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">   

            <com.sh.lynn.hz.chatview.BrokenlineView
                android:background="@android:color/darker_gray"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:layout_marginBottom="10dp"
                android:padding="10dp"
                android:layout_marginTop="10dp"
                android:id="@+id/brokenLine"
                android:layout_width="match_parent"
                android:layout_height="200dp"                       myChart:lineColor="@android:color/holo_red_light"                                                     myChart:bodyColor="@android:color/holo_green_light"
                myChart:textColor="@android:color/holo_red_light"
                />
        </LinearLayout>
    </ScrollView>
</LinearLayout>

在Activity中调用代码:

public class MainActivity extends AppCompatActivity {
    private List<Float> lineList;
    private BrokenlineView mBrokenlineView;

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

    private void initLine(){
        mBrokenlineView = (BrokenlineView)findViewById(R.id.brokenLine);
        Random random = new Random();
        lineList = new ArrayList<>();
        while (lineList.size() < 12) {
            int randomInt = random.nextInt(1000);
            lineList.add((float) randomInt-500);
        }
        mBrokenlineView.setPoints(lineList);
    }
}

四.总结

这种继承View的自定义控件其中关键就是 canvas的绘制功能,理解canvas基本上像这类绘制图的自定义控件都很容易完成。

自定义控件 遥控器 上下左右确定 控件

项目需求,需要画一个圆形的上下左右的要遥控器图片。如下图: 直接上代码了: package com.antiandi.android.circledemo; import android...
  • wkaia
  • wkaia
  • 2017年05月09日 19:15
  • 330

android 自定义控件--(圆盘形菜单控件)

思路原理: 定一个原点和一个半径,圆的四周均匀分布每个菜单。为了方便计算,菜单的坐标用度数表示,然后转化为极坐标计算。定某个点为起始点,根据总菜单数确定每个点增加的度数,然后依次确定每个点的度数,也...

Android开发笔记之自定义控件(物流时间轴的实现)

最近修改项目遇到查看物流这个需求,经过一个下午的时间的终于搞定,趁着这个时间点,赶快把这个功能抽取出来,方便大家以后开发的需要,帮助到更多的人 先看效果图,如下 看完之后,分析可知道,主要是两...

android 开发零起步学习笔记(六)Android 自定义控件开发

学习的以下三篇 Android 自定义控件开发入门(一) Android 自定义控件开发入门(二)   Android 自定义控件开发入门 (三)...
  • ingener
  • ingener
  • 2016年11月11日 19:09
  • 190
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android自定义控件开发笔记<一>
举报原因:
原因补充:

(最多只允许输入30个字)