Android鬼点子-上手自定义View | GreendaMi'Blog
Dots preloader
Android自定义View也算是Android程序猿们基本技能了,所以最近想试着上周做一做。
作为入门,就以一个简单的不响应触摸事件的View开始吧!
我在https://material.uplabs.com/posts/dots-preloader 上看到的下面的效果,那么动手实现一下吧 
- 在OnMeasure()方法中,获取属性值,测量自定义控件的大小,使自定义控件能够自适应布局各种各样的需求。
在attr.xml中
1 2 3 | <declare-styleable name="DotsPreloader"> <attr name="Circlecolor" format="color"></attr> </declare-styleable> |
在构造方法中获取属性,设置点点的颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.DotsPreloader); mColor = a.getColor(R.styleable.DotsPreloader_Circlecolor,Color.GRAY); a.recycle(); ``` 测量View的大小 ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) ``` 其中来自父控件的信息都是封装在widthMeasureSpec和heightMeasureSpec中,其中包含测量模式和具体的大小,由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低16位为specSize,由MeasureSpec.getSize()获取。 specMode一共有三种可能: MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。 MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。 MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。比如scollerView,因为是可以滚动的,所以长度是没有限制的。 最后测量的结果由下面这个方法设定 setMeasuredDimension(int measuredWidth, int measuredHeight) ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); mLayoutSize = Math.min(widthSpecSize,heightSpecSize); // 此处需要调用measureChild,子View才会被测量,在onLayout()中,才能取到子View长宽 // int cCount = getChildCount(); // for (int i = 0; i < cCount; i++) { // View child = getChildAt(i); // // 测量每一个child的宽和高 // measureChild(child, widthMeasureSpec, heightMeasureSpec); // // } //这里直接设成来自父控件大小的一个正方形 setMeasuredDimension(mLayoutSize, mLayoutSize); } |
- onLayout()是决定View在ViewGroup的位置。因为我们现在讨论的是View,没有子View需要排列,所以这一步其实我们不需要做额外的工作。插一句,对ViewGroup类,onLayout方法中,我们需要将所有子View的大小宽高设置好,这里大致说以layout的流程。
1 2 3 4 5 6 7 8 9 10 11 | getChildCount();//取到自子View的数量。 View childView = getChildAt(i);//取到子View的信息。 cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); cl = cParams.leftMargin; ct = cParams.topMargin; //最后设置 childView.layout(cl, ct, cr, cb); |
View中还有三个比较重要的方法
requestLayoutView重新调用一次layout过程。invalidateView重新调用一次draw过程forceLayout标识View在下一次重绘,需要重新调用layout过程。- 在OnDraw()方法中,来绘制要显示的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | protected void onDraw(Canvas canvas) { super.onDraw(canvas); //圆点的半径 double radius = (mLayoutSize/6) * 0.3; //圆点之间的缝隙 double gap = (mLayoutSize/6) * 0.4; //计算6个圆点的圆心坐标 List<Point> mCenterPoints = getCenterPoints(radius,gap); for(Point mCenterPoint : mCenterPoints){ canvas.drawCircle(mCenterPoint.x , mCenterPoint.y , (float) radius , mPain); } // postInvalidateDelayed(20); invalidate(); } private List<Point> getCenterPoints(double r, double g) { List<Point> mCenterPoints = new ArrayList<Point>(); float mCenterPointGap = new Float(2 * r + g); double a = (1-(1-time) * (1-time)) * mCenterPointGap; if(time > 1){ time = 0; //这里控制第一个圆点是在上面走还是下面走 if(flag == 0){ flag = 1; }else{ flag = 0; } }else{ time = time + 0.025; } //后5个点只是平移效果 int y = mLayoutSize/2; Float x2 = new Float((r + g)/2 + mCenterPointGap * 1 - a); Float x3 = new Float((r + g)/2 + mCenterPointGap * 2 - a); Float x4 = new Float((r + g)/2 + mCenterPointGap * 3 - a); Float x5 = new Float((r + g)/2 + mCenterPointGap * 4 - a); Float x6 = new Float((r + g)/2 + mCenterPointGap * 5 - a); Double x1 = 0.5 * g + r + (1-(1-time) * (1-time)) * mCenterPointGap * 5; //这里使用了x²+y²=r² Double y1 = Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)) + mLayoutSize * 0.5; if(flag == 0){ y1 = mLayoutSize * 0.5 - Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)); } mCenterPoints.add(new Point(x1.intValue(),y1.intValue())); mCenterPoints.add(new Point(x2.intValue(),y)); mCenterPoints.add(new Point(x3.intValue(),y)); mCenterPoints.add(new Point(x4.intValue(),y)); mCenterPoints.add(new Point(x5.intValue(),y)); mCenterPoints.add(new Point(x6.intValue(),y)); return mCenterPoints; } |
下面是完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | package com.example.dotspreloader; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.util.AttributeSet; import android.util.Log; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Created by GreendaMi on 2016/9/17. */ public class DotsPreloader extends View{ public int mColor; public Paint mPain = new Paint(); private int mLayoutSize = 100; double time = 0; int flag = 0; public DotsPreloader(Context context) { super(context); } public DotsPreloader(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.DotsPreloader); mColor = a.getColor(R.styleable.DotsPreloader_Circlecolor,Color.WHITE); a.recycle(); init(); } public DotsPreloader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private void init() { mPain.setAntiAlias(false); if (BuildConfig.DEBUG) Log.d("DotsPreloader", "mColor:" + mColor); mPain.setColor(mColor); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); mLayoutSize = Math.min(widthSpecSize,heightSpecSize); setMeasuredDimension(mLayoutSize, mLayoutSize); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); double radius = (mLayoutSize/6) * 0.3; double gap = (mLayoutSize/6) * 0.4; List<Point> mCenterPoints = getCenterPoints(radius,gap); for(Point mCenterPoint : mCenterPoints){ canvas.drawCircle(mCenterPoint.x , mCenterPoint.y , (float) radius , mPain); } // postInvalidateDelayed(20); invalidate(); } private List<Point> getCenterPoints(double r, double g) { List<Point> mCenterPoints = new ArrayList<Point>(); float mCenterPointGap = new Float(2 * r + g); double a = (1-(1-time) * (1-time)) * mCenterPointGap; if(time > 1){ time = 0; if(flag == 0){ flag = 1; }else{ flag = 0; } }else{ time = time + 0.025; } int y = mLayoutSize/2; Float x2 = new Float((r + g)/2 + mCenterPointGap * 1 - a); Float x3 = new Float((r + g)/2 + mCenterPointGap * 2 - a); Float x4 = new Float((r + g)/2 + mCenterPointGap * 3 - a); Float x5 = new Float((r + g)/2 + mCenterPointGap * 4 - a); Float x6 = new Float((r + g)/2 + mCenterPointGap * 5 - a); Double x1 = 0.5 * g + r + (1-(1-time) * (1-time)) * mCenterPointGap * 5; Double y1 = Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)) + mLayoutSize * 0.5; if(flag == 0){ y1 = mLayoutSize * 0.5 - Math.sqrt((mLayoutSize - g - 2 * r) * (mLayoutSize - g - 2 * r)/4 - (x1 - mLayoutSize * 0.5) * (x1 - mLayoutSize * 0.5)); } mCenterPoints.add(new Point(x1.intValue(),y1.intValue())); mCenterPoints.add(new Point(x2.intValue(),y)); mCenterPoints.add(new Point(x3.intValue(),y)); mCenterPoints.add(new Point(x4.intValue(),y)); mCenterPoints.add(new Point(x5.intValue(),y)); mCenterPoints.add(new Point(x6.intValue(),y)); return mCenterPoints; } } |
在value/attr.xml中
1 2 3 | <declare-styleable name="DotsPreloader"> <attr name="Circlecolor" format="color"></attr> </declare-styleable> |
布局文件中使用
1 2 3 4 5 6 7 8 | 引用自定义属性要加上下面这句话 xmlns:DotsPreloader="http://schemas.android.com/apk/res-auto" <com.example.dotspreloader.DotsPreloader android:layout_width="200dp" android:layout_height="200dp" DotsPreloader:Circlecolor="@color/myOrderColor"/> |
到此一个最简单的自定义View就完成了,比较有困都难的地方就是坐标的计算,可怜了我小学的体育老师啊~~~