> 文章列表 > Android 自定义view 入门 案例

Android 自定义view 入门 案例

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.SuperCircleViewstyle="@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 attributesfinal 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 objectmTextPaint = new TextPaint();mTextPaint.setFlags( Paint.ANTI_ALIAS_FLAG );mTextPaint.setTextAlign( Paint.Align.LEFT );// Update TextPaint and text measurements from attributesinvalidateTextPaintAndMeasurements();}private void invalidateTextPaintAndMeasurements() {mTextPaint.setTextSize( mExampleDimension );mTextPaint.setColor( mExampleColor );mTextWidth = mTextPaint.measureText( mExampleString );Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();mTextHeight = fontMetrics.bottom;}@Overrideprotected 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 attributesTypedArray 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);}@Overrideprotected 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;}@Overrideprotected 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() {@Overridepublic 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.SuperCircleViewstyle="@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"><TextViewandroid:id="@+id/tv_three"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="This is Three page"android:textSize="30sp" /><FrameLayoutandroid:layout_width="300dp"android:layout_height="300dp"android:layout_gravity="center"><com.lan.lanidemo.customeview.SuperCircleViewandroid: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" /><TextViewandroid: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" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginTop="10dp"android:orientation="horizontal"><TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0"android:textColor="#506946"android:textSize="80sp" /><TextViewandroid: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;@Overrideprotected 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() {@Overridepublic void onClick(View v) {//随机设定圆环大小int i = new Random().nextInt(100) + 1;Log.i(TAG, "onClick: i::" + i);mSuperCircleView.setValue(i, mTextView);}});}
}

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