Android 之路70---自定义View

导读

1.简介
2.基本实现
自定义属性的声明与获取
测量onMeasure
绘制onDraw
状态的存储与恢复
3.案例:圆形进度条

简介

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

基本实现

这里写图片描述

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<!--declare-styleable name默认与自定义类的名字相同-->
<resources>
    <declare-styleable name="TestView">
        <attr name="test_boolean" format="boolean"></attr>
        <attr name="test_string" format="string"></attr>
        <attr name="test_integer" format="integer"></attr>
        <attr name="test_enum" format="enum">
            <enum name="top" value="1"></enum>
            <enum name="bottom" value="2"></enum>
        </attr>
        <attr name="test_dimension" format="dimension"></attr>
    </declare-styleable>
</resources>

TestView.java

package com.hala.custom_view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class TestView extends View {

    private Paint mPaint;
    private String mText="Emilia";

    public TestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        initPaint();

        TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.TestView);
       boolean booleanTest=ta.getBoolean(R.styleable.TestView_test_boolean,false);
       int integerTest=ta.getInteger(R.styleable.TestView_test_integer,-1);
       float dimensionTest=ta.getDimension(R.styleable.TestView_test_dimension,0);
       int enumTest=ta.getInt(R.styleable.TestView_test_enum,1);
       String StringTest=ta.getString(R.styleable.TestView_test_string);

       //第二种方法:这种方法的好处是如果不设置初始值会使用默认的,而不会返回null,比较安全
//        int count = ta.getIndexCount();
//        for (int i = 0; i < count; i++)
//        {
//            int index = ta.getIndex(i);
//            switch (index)
//            {
//                case R.styleable.TestView_test_string:
//                    mText = ta.getString(R.styleable.TestView_test_string);
//                    break;
//            }
//        }



        Log.e("TAG", booleanTest + " , "
                + integerTest + " , "
                + dimensionTest + " , " + enumTest + " ," + StringTest);

        ta.recycle();
    }


    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //父控件传入的参数
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);

        int width = 0;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
            if (widthMode == MeasureSpec.AT_MOST)
            {
                width = Math.min(needWidth, widthSize);
            } else
            {
                width = needWidth;
            }
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int height = 0;

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
            if (heightMode == MeasureSpec.AT_MOST)
            {
                height = Math.min(needHeight, heightSize);
            } else //MeasureSpec.UNSPECIFIED
            {
                height = needHeight;
            }
        }
        setMeasuredDimension(width, height);
    }

    private int measureHeight()
    {
        return 0;
    }

    private int measureWidth()
    {
        return 0;
    }


    //初始化圆
    private void initPaint(){
        mPaint=new Paint();
        //圆的类型STROKE 为空心 FILL为实心
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(6);
        mPaint.setColor(0xffff0000);
        mPaint.setAntiAlias(true);
    }

    /**
     * 绘制
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画圆
        canvas.drawCircle(getWidth()/2,getHeight()/2,getWidth()/2-mPaint.getStrokeWidth()/2,mPaint);
        //画线
        mPaint.setStrokeWidth(1);
        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,mPaint);
        canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),mPaint);
        //绘制文本 文本是以基线对齐的
        mPaint.setTextSize(72);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(0);
        canvas.drawText(mText,0,mText.length(),0,getHeight(),mPaint);

    }

    //触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        mText = "8888";
        invalidate();
        return true;
    }


    private static final String INSTANCE = "instance";
    private static final String KEY_TEXT = "key_text";

    /**
     * 存储状态
     * @return
     */
    @Override
    protected Parcelable onSaveInstanceState()
    {
        Bundle bundle = new Bundle();
        //存储自己的设置
        bundle.putString(KEY_TEXT, mText);
        //存储父类的设置
        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
        return bundle;
    }

    /**
     * 注意重新获得布局中控件一定要设置id属性
     * 重新获得状态
     * @param state
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state)
    {
        if (state instanceof Bundle)
        {
            Bundle bundle = (Bundle) state;
            Parcelable parcelable = bundle.getParcelable(INSTANCE);
            super.onRestoreInstanceState(parcelable);
            mText = bundle.getString(KEY_TEXT);
            return;
        }
        super.onRestoreInstanceState(state);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<!--xmlns:app 自定义view要写自己的标签,其中app可以自定义 结尾要是res-auto-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.hala.custom_view.TestView
        android:id="@+id/id_pb"
        android:background="#ff0000"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:test_boolean="true"
        app:test_dimension="100dp"
        app:test_enum="bottom"
        app:test_integer="10086"
        app:test_string="Emilia" />
</android.support.constraint.ConstraintLayout>

这里写图片描述

案例:圆形进度条

这里写图片描述

这里写图片描述

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<!--declare-styleable name默认与自定义类的名字相同-->
<resources>

    <declare-styleable name="RoundProgressBar">
        <attr name="color" format="color"></attr>
        <attr name="line_width" format="dimension"></attr>
        <attr name="radius" format="dimension"></attr>
        <attr name="android:progress" ></attr>
        <attr name="android:textSize" ></attr>
    </declare-styleable>

</resources>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<!--xmlns:app 自定义view要写自己的标签,其中app可以自定义 结尾要是res-auto-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.hala.custom_view.RoundProgressBar
        android:id="@+id/id_pb"
        android:progress="0"
        android:textSize="16sp"
        app:color="#d201f7"
        app:radius="40dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</android.support.constraint.ConstraintLayout>

RoundProgressBar.java

package com.hala.custom_view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

public class RoundProgressBar extends View {

    private int mRadius;
    private int mColor;
    private int mLineWidth;
    private int mTextSize;
    private int mProgress;

    private Paint mPaint;

    public RoundProgressBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);


        TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar);

        mRadius = (int) ta.getDimension(R.styleable.RoundProgressBar_radius, dp2px(30));
        mColor = ta.getColor(R.styleable.RoundProgressBar_color, 0xffff0000);
        mLineWidth = (int) ta.getDimension(R.styleable.RoundProgressBar_line_width, dp2px(3));
        mTextSize = (int) ta.getDimension(R.styleable.RoundProgressBar_android_textSize, dp2px(36));
        mProgress = ta.getInt(R.styleable.RoundProgressBar_android_progress, 30);

        ta.recycle();

        //初始化要放在属性获取之后
        initPaint();
    }

    //自定义方法 用来设置默认值
    private float dp2px(int dpVal)
    {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
    }

    //初始化圆
    private void initPaint(){
        mPaint=new Paint();
        mPaint.setColor(mColor);
        mPaint.setAntiAlias(true);
    }


    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //父控件传入的参数
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);

        int width = 0;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
            if (widthMode == MeasureSpec.AT_MOST)
            {
                width = Math.min(needWidth, widthSize);
            } else
            {
                width = needWidth;
            }
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int height = 0;

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
            if (heightMode == MeasureSpec.AT_MOST)
            {
                height = Math.min(needHeight, heightSize);
            } else //MeasureSpec.UNSPECIFIED
            {
                height = needHeight;
            }
        }

        width=Math.min(width,height);
        setMeasuredDimension(width, height);
    }

    private int measureHeight()
    {
        return mRadius*2;
    }

    private int measureWidth()
    {
        return mRadius*2;
    }




    /**
     * 绘制
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制细圆
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mLineWidth*1.0f/4);

        int width=getWidth();
        int height=getHeight();

        canvas.drawCircle(width/2,height/2,
                width/2-getPaddingLeft()-mPaint.getStrokeWidth()/2,mPaint);

        //绘制圆弧
        mPaint.setStrokeWidth(mLineWidth);
        canvas.save();
        canvas.translate(getPaddingLeft(),getPaddingTop());
        float angle=mProgress*1.0f/100*360;
        canvas.drawArc(new RectF(mPaint.getStrokeWidth()/2,mPaint.getStrokeWidth()/2,width-getPaddingLeft()*2-mPaint.getStrokeWidth()/2,height-getPaddingLeft()*2-mPaint.getStrokeWidth()/2),
                0,angle,false,mPaint);
        canvas.restore();

        //绘制文本
        String text=mProgress+"%";
        mPaint.setStrokeWidth(0);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
        int y = getHeight() / 2;
        Rect bound = new Rect();
        mPaint.getTextBounds(text, 0, text.length(), bound);
        int textHeight = bound.height();
        canvas.drawText(text, 0, text.length(), getWidth() / 2, y + textHeight / 2, mPaint);
//        这里其实是以文字的基线为准的,如果要除去这部分影响,要用下边这个
//        canvas.drawText(text, 0, text.length(), getWidth() / 2, y + textHeight / 2-mPaint.descent(), mPaint);
    }


    public void setProgress(int progress)
    {
        mProgress = progress;
        invalidate();
    }

    public int getProgress()
    {
        return mProgress;
    }


    private static final String INSTANCE = "instance";
    private static final String KEY_TEXT = "key_progress";

    /**
     * 存储状态
     * @return
     */
    @Override
    protected Parcelable onSaveInstanceState()
    {
        Bundle bundle = new Bundle();
        //存储自己的设置
        bundle.putInt(KEY_TEXT, mProgress);
        //存储父类的设置
        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
        return bundle;
    }

    /**
     * 注意重新获得布局中控件一定要设置id属性
     * 重新获得状态
     * @param state
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state)
    {
        if (state instanceof Bundle)
        {
            Bundle bundle = (Bundle) state;
            Parcelable parcelable = bundle.getParcelable(INSTANCE);
            super.onRestoreInstanceState(parcelable);
            mProgress = bundle.getInt(KEY_TEXT);
            return;
        }
        super.onRestoreInstanceState(state);
    }
}

MainActivity.java

package com.hala.custom_view;

import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

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

        final View view = findViewById(R.id.id_pb);
        view.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                ObjectAnimator.ofInt(view, "progress", 0, 100).setDuration(3000).start();
            }
        });
    }
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值