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

继承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基本上像这类绘制图的自定义控件都很容易完成。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值