Android 自定义view 入门 案例

自定义一个圆环进度条:

1.首页Android Studio创建一个项目

2.在项目src/xxx/目录下右键选择创建一个自定义view页面:new->UICompoent->customer view

 3.输入自定义名称,选择开发语言

 4.确定之后,自动生成3个文件一个是:

第一个是逻辑代码:com.lan.lanidemo.customeview.SuperCircleView.class
第二个是布局: src\main\res\layout\sample_super_circle_view.xml
第三个是view需要使用的属性:src\main\res\values\attrs_super_circle_view.xml
      (通过这种方式创建属性,多次创建可能造成一些变量重复定义。)

attrs_super_circle_view.xml源码: 

<resources>
    <declare-styleable name="SuperCircleView">
        <attr name="exampleString" format="string" />
        <attr name="exampleDimension" format="dimension" />
        <attr name="exampleColor" format="color" />
        <attr name="exampleDrawable" format="color|reference" />
    </declare-styleable>
</resources>

其中declare-styeable节点的name属性值一般是你写的view的名字,如这里根据命名默认生成:SuperCirecleView。接下来定义可以在xml中定义的组件属性,在后面我会根据需要增加圆环宽度颜色等。其中format属性指定可接受值的类型,多个类型用“|”分隔。 

 sample_super_circle_view.xml布局源码

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.lan.lanidemo.customeview.SuperCircleView
        style="@style/Widget.Theme.LaniDemo.SuperCircleView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:paddingLeft="20dp"
        android:paddingBottom="40dp"
        app:exampleDimension="24sp"
        app:exampleDrawable="@android:drawable/ic_menu_add"
        app:exampleString="Hello, SuperCircleView" />

</FrameLayout>

SuperCircleView.class 

package com.lan.lanidemo.customeview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

import com.lan.lanidemo.R;

/**
 * TODO: document your custom view class.
 */
public class SuperCircleView extends View {
    private String mExampleString; // TODO: use a default from R.string...
    private int mExampleColor = Color.RED; // TODO: use a default from R.color...
    private float mExampleDimension = 0; // TODO: use a default from R.dimen...
    private Drawable mExampleDrawable;

    private TextPaint mTextPaint;
    private float mTextWidth;
    private float mTextHeight;

    public SuperCircleView(Context context) {
        super( context );
        init( null, 0 );
    }

    public SuperCircleView(Context context, AttributeSet attrs) {
        super( context, attrs );
        init( attrs, 0 );
    }

    public SuperCircleView(Context context, AttributeSet attrs, int defStyle) {
        super( context, attrs, defStyle );
        init( attrs, defStyle );
    }

    private void init(AttributeSet attrs, int defStyle) {
        // Load attributes
        final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.SuperCircleView, defStyle, 0 );

        mExampleString = a.getString(
                R.styleable.SuperCircleView_exampleString );
        mExampleColor = a.getColor(
                R.styleable.SuperCircleView_exampleColor,
                mExampleColor );
        // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
        // values that should fall on pixel boundaries.
        mExampleDimension = a.getDimension(
                R.styleable.SuperCircleView_exampleDimension,
                mExampleDimension );

        if (a.hasValue( R.styleable.SuperCircleView_exampleDrawable )) {
            mExampleDrawable = a.getDrawable(
                    R.styleable.SuperCircleView_exampleDrawable );
            mExampleDrawable.setCallback( this );
        }

        a.recycle();

        // Set up a default TextPaint object
        mTextPaint = new TextPaint();
        mTextPaint.setFlags( Paint.ANTI_ALIAS_FLAG );
        mTextPaint.setTextAlign( Paint.Align.LEFT );

        // Update TextPaint and text measurements from attributes
        invalidateTextPaintAndMeasurements();
    }

    private void invalidateTextPaintAndMeasurements() {
        mTextPaint.setTextSize( mExampleDimension );
        mTextPaint.setColor( mExampleColor );
        mTextWidth = mTextPaint.measureText( mExampleString );

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        mTextHeight = fontMetrics.bottom;
    }

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

        // TODO: consider storing these as member variables to reduce
        // allocations per draw cycle.
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        int contentWidth = getWidth() - paddingLeft - paddingRight;
        int contentHeight = getHeight() - paddingTop - paddingBottom;

        // Draw the text.
        canvas.drawText( mExampleString,
                paddingLeft + (contentWidth - mTextWidth) / 2,
                paddingTop + (contentHeight + mTextHeight) / 2,
                mTextPaint );

        // Draw the example drawable on top of the text.
        if (mExampleDrawable != null) {
            mExampleDrawable.setBounds( paddingLeft, paddingTop,
                    paddingLeft + contentWidth, paddingTop + contentHeight );
            mExampleDrawable.draw( canvas );
        }
    }

    /**
     * Gets the example string attribute value.
     *
     * @return The example string attribute value.
     */
    public String getExampleString() {
        return mExampleString;
    }

    /**
     * Sets the view"s example string attribute value. In the example view, this string
     * is the text to draw.
     *
     * @param exampleString The example string attribute value to use.
     */
    public void setExampleString(String exampleString) {
        mExampleString = exampleString;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example color attribute value.
     *
     * @return The example color attribute value.
     */
    public int getExampleColor() {
        return mExampleColor;
    }

    /**
     * Sets the view"s example color attribute value. In the example view, this color
     * is the font color.
     *
     * @param exampleColor The example color attribute value to use.
     */
    public void setExampleColor(int exampleColor) {
        mExampleColor = exampleColor;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example dimension attribute value.
     *
     * @return The example dimension attribute value.
     */
    public float getExampleDimension() {
        return mExampleDimension;
    }

    /**
     * Sets the view"s example dimension attribute value. In the example view, this dimension
     * is the font size.
     *
     * @param exampleDimension The example dimension attribute value to use.
     */
    public void setExampleDimension(float exampleDimension) {
        mExampleDimension = exampleDimension;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example drawable attribute value.
     *
     * @return The example drawable attribute value.
     */
    public Drawable getExampleDrawable() {
        return mExampleDrawable;
    }

    /**
     * Sets the view"s example drawable attribute value. In the example view, this drawable is
     * drawn above the text.
     *
     * @param exampleDrawable The example drawable attribute value to use.
     */
    public void setExampleDrawable(Drawable exampleDrawable) {
        mExampleDrawable = exampleDrawable;
    }
}

5.在这里我是做一圆环进度条:如本文开始图片效果,更新代码如下。

 6. 更新自定义属性:

attrs_super_circle_view.xml

<resources>
    <declare-styleable name="SuperCircleView">
        <attr name="exampleString" format="string" />
        <attr name="exampleDimension" format="dimension" />
        <attr name="exampleColor" format="color" />
        <attr name="exampleDrawable" format="color|reference" />
        <!-- 圆的半径 -->
        <attr name="min_circle_radio" format="integer" />

        <!-- 圆环的宽度 -->
        <attr name="ring_width" format="float" />

        <!-- 内圆的颜色 -->
        <attr name="circle_color" format="color" />

        <!-- 外圆的颜色 -->
        <attr name="max_circle_color" format="color" />

        <!-- 圆环的默认颜色 -->
        <attr name="ring_normal_color" format="color" />

        <!-- 圆环要显示的彩色的区域(随着数值的改变,显示不同大小的彩色区域)-->
        <attr name="ring_color_select" format="integer" />

        <!-- 绘制内容的数值 -->
        <attr name="maxValue" format="integer" />
        <attr name="value" format="integer" />
        <attr name="ring_radius" format="float" />

    </declare-styleable>
</resources>

7.更新自定义view控制代码:

SuperCircleView.class

package com.lan.lanidemo.customeview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.lan.lanidemo.R;

import static android.content.ContentValues.TAG;

/**
 * TODO: document your custom view class.
 */
public class SuperCircleView extends View {
    private String mExampleString; // TODO: use a default from R.string...
    private int mExampleColor = Color.RED; // TODO: use a default from R.color...
    private float mExampleDimension = 0; // TODO: use a default from R.dimen...
    private Drawable mExampleDrawable;

    private TextPaint mTextPaint;
    private float mTextWidth;
    private float mTextHeight;
    private ValueAnimator valueAnimator;
    private int mViewCenterX;   //view宽的中心点(可以暂时理解为圆心)
    private int mViewCenterY;   //view高的中心点(可以暂时理解为圆心)

    private int mMinRadio; //最里面白色圆的半径
    private float mRingWidth; //圆环的宽度
    private int mMinCircleColor;    //最里面圆的颜色
    private int mRingNormalColor;    //默认圆环的颜色
    private Paint mPaint;
    private int color[] = new int[3];   //渐变颜色

    private RectF mRectF; //圆环的矩形区域
    private int mSelectRing = 0; //要显示的彩色区域(岁数值变化)
    float ringRadius;
    private int mMaxValue;

    public SuperCircleView(Context context) {
        super( context );
        Log.d( TAG, "SuperCircleView: >>构造" );

        init( null, 0 );
    }

    public SuperCircleView(Context context, AttributeSet attrs) {
        super( context, attrs );
        Log.d( TAG, "SuperCircleView: >>构造2" );

        init( attrs, 0 );

    }

    public SuperCircleView(Context context, AttributeSet attrs, int defStyle) {
        super( context, attrs, defStyle );
        init( attrs, defStyle );
    }

    private void init(AttributeSet attrs, int defStyle) {
        // Load attributes

        TypedArray a =  getContext().obtainStyledAttributes(attrs, R.styleable.SuperCircleView);
        //最里面白色圆的半径
        mMinRadio = a.getInteger(R.styleable.SuperCircleView_min_circle_radio, 300);
        //圆环宽度
        mRingWidth = a.getFloat(R.styleable.SuperCircleView_ring_width, 40);

        //最里面的圆的颜色(绿色)
        mMinCircleColor = a.getColor(R.styleable.SuperCircleView_circle_color,
                getContext().getResources().getColor(R.color.green));
        //圆环的默认颜色(圆环占据的是里面的圆的空间)
        mRingNormalColor = a.getColor(R.styleable.SuperCircleView_ring_normal_color,
                getContext().getResources().getColor(R.color.gray));
        //圆环要显示的彩色的区域
        mSelectRing = a.getInt(R.styleable.SuperCircleView_ring_color_select, 0);

        mMaxValue = a.getInt(R.styleable.SuperCircleView_maxValue, 100);
        ringRadius = a.getDimension( R.styleable.SuperCircleView_ring_radius,  dp2px(100)  );

        a.recycle();

        //抗锯齿画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //防止边缘锯齿
        mPaint.setAntiAlias(true);
        //需要重写onDraw就得调用此
        this.setWillNotDraw(false);

        //圆环渐变的颜色
        color[0] = Color.parseColor("#FFD300");
        color[1] = Color.parseColor("#FF0084");
        color[2] = Color.parseColor("#16FF00");
    }

    public int dp2px(float dpValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //view的宽和高,相对于父布局(用于确定圆心)
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();
        mViewCenterX = viewWidth / 2;
        mViewCenterY = viewHeight / 2;
        mRectF = new RectF(
                mViewCenterX - ringRadius,
                mViewCenterY - ringRadius,
                mViewCenterX + ringRadius,
                mViewCenterY + ringRadius
        );
    }

    private void invalidateTextPaintAndMeasurements() {
        mTextPaint.setTextSize( mExampleDimension );
        mTextPaint.setColor( mExampleColor );
        mTextWidth = mTextPaint.measureText( mExampleString );

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        mTextHeight = fontMetrics.bottom;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw( canvas );
        mPaint.setColor(mMinCircleColor);
        canvas.drawCircle(mViewCenterX, mViewCenterY, mMinRadio, mPaint);
        //画默认圆环
        drawNormalRing(canvas);
        //画彩色圆环
        drawColorRing(canvas);
    }
    /**
     * 画默认圆环
     *
     * @param canvas
     */
    private void drawNormalRing(Canvas canvas) {
        Paint ringNormalPaint = new Paint(mPaint);
        ringNormalPaint.setStyle(Paint.Style.STROKE);
        ringNormalPaint.setStrokeWidth(mRingWidth);
        ringNormalPaint.setColor(mRingNormalColor);//圆环默认颜色为灰色
        canvas.drawArc(mRectF, 360, 360, false, ringNormalPaint);
    }

    /**
     * 画彩色圆环
     *
     * @param canvas
     */
    private void drawColorRing(Canvas canvas) {
        Paint ringColorPaint = new Paint(mPaint);
        ringColorPaint.setStyle(Paint.Style.STROKE);
        ringColorPaint.setStrokeWidth(mRingWidth);
        ringColorPaint.setShader(new SweepGradient(mViewCenterX, mViewCenterX, color, null));
        //逆时针旋转90度
        canvas.rotate(-90, mViewCenterX, mViewCenterY);
        canvas.drawArc(mRectF, 360, mSelectRing, false, ringColorPaint);
        ringColorPaint.setShader(null);
    }

    //***************************************用于更新圆环表示的数值*****************************************************
    /**
     * 设置当前值
     *
     * @param value
     */
    public void setValue(int value,TextView textView) {
        if (value > mMaxValue) {
            value = mMaxValue;
        }
        int start = 0;
        int end = value;
        startAnimator(start, end, 2000,textView);
    }

    private void startAnimator(int start, int end, long animTime, final TextView textView) {
        valueAnimator = ValueAnimator.ofInt(start, end);
        valueAnimator.setDuration(animTime);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Log.i(TAG, "onAnimationUpdate: animation.getAnimatedValue()::"+animation.getAnimatedValue());
                int i = Integer.valueOf(String.valueOf(animation.getAnimatedValue()));
                textView.setText(i + "");
                //每个单位长度占多少度
                mSelectRing=(int) (360 * (i / 100f));
                Log.i(TAG, "onAnimationUpdate: mSelectRing::"+mSelectRing);
                invalidate();
            }
        });
        valueAnimator.start();
    }


    /**
     * Gets the example string attribute value.
     *
     * @return The example string attribute value.
     */
    public String getExampleString() {
        return mExampleString;
    }

    /**
     * Sets the view"s example string attribute value. In the example view, this string
     * is the text to draw.
     *
     * @param exampleString The example string attribute value to use.
     */
    public void setExampleString(String exampleString) {
        mExampleString = exampleString;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example color attribute value.
     *
     * @return The example color attribute value.
     */
    public int getExampleColor() {
        return mExampleColor;
    }

    /**
     * Sets the view"s example color attribute value. In the example view, this color
     * is the font color.
     *
     * @param exampleColor The example color attribute value to use.
     */
    public void setExampleColor(int exampleColor) {
        mExampleColor = exampleColor;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example dimension attribute value.
     *
     * @return The example dimension attribute value.
     */
    public float getExampleDimension() {
        return mExampleDimension;
    }

    /**
     * Sets the view"s example dimension attribute value. In the example view, this dimension
     * is the font size.
     *
     * @param exampleDimension The example dimension attribute value to use.
     */
    public void setExampleDimension(float exampleDimension) {
        mExampleDimension = exampleDimension;
        invalidateTextPaintAndMeasurements();
    }

    /**
     * Gets the example drawable attribute value.
     *
     * @return The example drawable attribute value.
     */
    public Drawable getExampleDrawable() {
        return mExampleDrawable;
    }

    /**
     * Sets the view"s example drawable attribute value. In the example view, this drawable is
     * drawn above the text.
     *
     * @param exampleDrawable The example drawable attribute value to use.
     */
    public void setExampleDrawable(Drawable exampleDrawable) {
        mExampleDrawable = exampleDrawable;
    }
}

8.更新自定义布局:sample_super_circle_view.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.lan.lanidemo.customeview.SuperCircleView
        style="@style/Widget.Theme.LaniDemo.MyView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:paddingLeft="20dp"
        android:paddingBottom="40dp"
        app:exampleDimension="24sp"
        app:exampleDrawable="@android:drawable/ic_menu_add"
        app:exampleString="Hello, SuperCircleView" />

</FrameLayout>

9.最后在新建一个页面:ThreeActivity, 并在AndroidManifest.xml,设置为启动页面。创建完成后,在xml添加自定义view:activity_three.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"


    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_three"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is Three page"
        android:textSize="30sp" />

    <FrameLayout
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_gravity="center">

        <com.lan.lanidemo.customeview.SuperCircleView
            android:id="@+id/superview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            app:maxValue="100"
            app:ring_width="60"
            app:value="20" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="60dp"
            android:text="信息完成度"
            android:textColor="#CFD5DE"
            android:textSize="18sp" />
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                android:textColor="#506946"
                android:textSize="80sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="%"
                android:textSize="28sp" />
        </LinearLayout>

    </FrameLayout>

</LinearLayout>

10. ThreeActivity.class 在启动页设置百分比。 同时增加一个点击事件,点击view可以随机变化百分比值。

package com.lan.lanidemo;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.lan.lanidemo.customeview.SuperCircleView;

import java.util.Random;

import androidx.appcompat.app.AppCompatActivity;

public class ThreeActivity extends AppCompatActivity {

    private static final String TAG = "ThreeActivity";
    private TextView mTextView;
    SuperCircleView mSuperCircleView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_three );
        initView();


    }

    private void initView() {
        mTextView = (TextView) findViewById( R.id.tv );
        mSuperCircleView = findViewById(R.id.superview);
        mSuperCircleView.setValue(70, mTextView);
        mSuperCircleView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //随机设定圆环大小
                int i = new Random().nextInt(100) + 1;
                Log.i(TAG, "onClick: i::" + i);
                mSuperCircleView.setValue(i, mTextView);
            }
        });
    }
}

11.最后运行,点击之后进度更新。

猜你喜欢

转载自blog.csdn.net/LlanyW/article/details/130083767