上一篇 Android-自定义View-onMeasure方法
我们继续....
之前我们针对控件大小做了重新测量,同时兼容了下wrap_content等问题。现在还需要做半径的处理,也就是需要根据最后计算得到的控件宽高作为实际绘制的参考, 同时还需要约束半径:
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
///< 2.进行绘制
///< 先绘制一个背景 - 如果自定义控件背景存在的情况下,则进行背景绘制
if (null != bgDrawable) {
canvas.drawBitmap(bgDrawable, 0, 0, paint);
}
///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
canvas.drawCircle(width / 2, height / 2,
radius + changeRadius, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
///< 采用默认的onMeasure看看
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
///< 自己进行相关测量
int defaultW = 12;
int defaultH = 12;
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
///< 在wrap_content的情况下默认长度为默认宽/高与背景图片对比后的最大值,假设背景图片是200*200,则显示200*200
int minWSize = Math.max(dp2px(context, defaultW), getSuggestedMinimumWidth());
int minHSize = Math.max(dp2px(context, defaultH), getSuggestedMinimumHeight());
///< wrap_content的specMode是AT_MOST模式,这种情况下宽/高等同于specSize
// 查表得这种情况下specSize等同于parentSize,也就是父容器当前剩余的大小
// 在wrap_content的情况下如果不特殊处理,效果等同martch_parent
///< 打印看看妮
// switch (wSpecMode) {
// case UNSPECIFIED: ///< 父控件没有针对子控件进行大小限制
// Log.e("test", "w's UNSPECIFIED");
// break;
// case EXACTLY: ///< 父控件决定了子控件准确的尺寸
// Log.e("test", "w's EXACTLY");
// break;
// case AT_MOST: ///< 子控件会成为一定的尺寸大小
// Log.e("test", "w's AT_MOST");
// break;
// }
//
// switch (hSpecMode) {
// case UNSPECIFIED:
// Log.e("test", "h's UNSPECIFIED");
// break;
// case EXACTLY:
// Log.e("test", "h's EXACTLY");
// break;
// case AT_MOST:
// Log.e("test", "h's AT_MOST");
// break;
// }
///< 进行控件尺寸设置,同时更新绘制宽高
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = minHSize;
setMeasuredDimension(minWSize, minHSize);
} else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
width = wSize;
height = hSize;
setMeasuredDimension(wSize, hSize);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = hSize;
setMeasuredDimension(minWSize, hSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
width = wSize;
height = minHSize;
setMeasuredDimension(wSize, minHSize);
}
///< 做一个兼容,如果半径超过了控件宽或者高
int minWH = width;
if (width > height) {
minWH = height;
}
if ((radius * 2) > minWH) {
radius = minWH / 2;
Log.e("attrs", "纠正一下 " + radius);
}
}
反正目前来看,没什么问题,理解的浅或者不对的也就先这样看到起。完事了我们继续深入完善纠正就是了...
image在一开始小白在onDraw方法里面有去用getWidth()或者getHeight()去获取控件的宽高,但是由于当时没有进行onMeasure()的计算,所以获取的宽高都是0,肯定用这个宽高进行绘制时错误的啦!
而现在我们再用这个去获取宽高,同时纠正半径的范围问题,那肯定就OK啦!
布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<!--android:background="@drawable/luffy"-->
<!--android:minWidth=""-->
<!--android:minHeight=""-->
<!--app:bgdrawable="@drawable/luffy"-->
<!--android:minWidth="12dp"-->
<me.heyclock.hl.customcopy.MyTextView01
style="@style/MyTextView01"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
app:ccolor="#F50808"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:radius="1000dp" />
</android.support.constraint.ConstraintLayout>
测量和绘制部分
image<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">绘制时进行宽高获取和半径约束</figcaption>
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
///< 2.进行绘制
///< 先绘制一个背景 - 如果自定义控件背景存在的情况下,则进行背景绘制
if (null != bgDrawable) {
canvas.drawBitmap(bgDrawable, 0, 0, paint);
}
///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
///< 做一个兼容,如果半径超过了控件宽或者高
int minWH = getWidth();
if (getWidth() > getHeight()) {
minWH = getHeight();
}
if ((radius * 2) > minWH) {
radius = minWH / 2;
Log.e("attrs", "纠正一下 " + radius);
}
canvas.drawCircle(getWidth() / 2, getHeight() / 2,
radius + changeRadius, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
///< 采用默认的onMeasure看看
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
///< 自己进行相关测量
int defaultW = 12;
int defaultH = 12;
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
///< 在wrap_content的情况下默认长度为默认宽/高与背景图片对比后的最大值,假设背景图片是200*200,则显示200*200
int minWSize = Math.max(dp2px(context, defaultW), getSuggestedMinimumWidth());
int minHSize = Math.max(dp2px(context, defaultH), getSuggestedMinimumHeight());
///< wrap_content的specMode是AT_MOST模式,这种情况下宽/高等同于specSize
// 查表得这种情况下specSize等同于parentSize,也就是父容器当前剩余的大小
// 在wrap_content的情况下如果不特殊处理,效果等同martch_parent
///< 打印看看妮
// switch (wSpecMode) {
// case UNSPECIFIED: ///< 父控件没有针对子控件进行大小限制
// Log.e("test", "w's UNSPECIFIED");
// break;
// case EXACTLY: ///< 父控件决定了子控件准确的尺寸
// Log.e("test", "w's EXACTLY");
// break;
// case AT_MOST: ///< 子控件会成为一定的尺寸大小
// Log.e("test", "w's AT_MOST");
// break;
// }
//
// switch (hSpecMode) {
// case UNSPECIFIED:
// Log.e("test", "h's UNSPECIFIED");
// break;
// case EXACTLY:
// Log.e("test", "h's EXACTLY");
// break;
// case AT_MOST:
// Log.e("test", "h's AT_MOST");
// break;
// }
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = minHSize;
setMeasuredDimension(minWSize, minHSize);
} else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
width = wSize;
height = hSize;
setMeasuredDimension(wSize, hSize);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = hSize;
setMeasuredDimension(minWSize, hSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
width = wSize;
height = minHSize;
setMeasuredDimension(wSize, minHSize);
}
///< 做一个兼容,如果半径超过了控件宽或者高
// int minWH = width;
// if (width > height) {
// minWH = height;
// }
// if ((radius * 2) > minWH) {
// radius = minWH / 2;
// Log.e("attrs", "纠正一下 " + radius);
// }
}
当然如果你不想每次绘制都去做半径测处理,也可以在测量里面就把这些处理好. 调试过程中,我发现onMeasure()会运行两次?
image关于这个有hyman的解释:
你好,这个官方文档有一定的解释,
地址:http://developer.android.com/guide/topics/ui/how-android-draws.html ;
中文翻译地址:http://blog.csdn.net/jewleo/article/details/39547631 。
stackoverflow中也有很多类似的问题,你可以看下大家的解答。
具体的我大概看了下,就是关于可能的多次测量最终得到控件的尺寸。相信后面我再自定义ViewGroup时应该会比较明显的体验到。这里暂时做个记录....
最后我们拷贝一份之前的自定义控件类作为备份,整理下代码:
package me.heyclock.hl.customcopy;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
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;
import java.util.Timer;
import java.util.TimerTask;
/*
*@Description: 自定义绘制文本
*@Author: hl
*@Time: 2018/10/12 9:37
*/
public class MyTextView01 extends View {
/* 官方文档:
https://developer.android.google.cn/reference/android/graphics/Canvas
https://developer.android.google.cn/reference/android/graphics/Paint
*/
private Context context;///< 上下文
private Canvas canvas; ///< 画布
private Paint paint; ///< 画笔
///< 做红色点击区域限制
private boolean bIsDownInRedRegion = false;
///< 定时刷新
private Timer timer = null;
///< 圆圈半径
private int radius;
///< 圆圈颜色
private String color;
///< 控件自定义背景
private Bitmap bgDrawable = null;
///< 控件宽度和高度
private int width = 12;
private int height = 12;
/**
* 刷新绘制+增量变化
*/
private static final int STEP_RADIUS = 10; ///< 每次半径增加10
private int changeRadius = 0; ///< 变化量记录,达到50时则开始减;达到0就开始增加
private boolean addFlag = true; ///< 标记是否增加增量
public MyTextView01(Context context) {
this(context, null);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, 0, 0);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
///< TypedArray的方式
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView01);
///< getDimension() getDimensionPixelOffset() getDimensionPixelSize()
/// --这三个方法都是根据DisplayMetrics获取相应的值,不同在于方法1直接保存float型数据,方法2直接对float取整,方法3对float小数先四舍五入后取整。
radius = ta.getDimensionPixelOffset(R.styleable.MyTextView01_radius, 6);
color = ta.getString(R.styleable.MyTextView01_ccolor);
Drawable drawable = ta.getDrawable(R.styleable.MyTextView01_bgdrawable);
if (null != drawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
bgDrawable = bd.getBitmap();
}
ta.recycle();
///< 1\. 做一些绘制初始化
canvas = new Canvas(); ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
paint = new Paint();
paint.setColor(Color.parseColor(color));
}
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
///< 2.进行绘制
///< 先绘制一个背景 - 如果自定义控件背景存在的情况下,则进行背景绘制
if (null != bgDrawable) {
canvas.drawBitmap(bgDrawable, 0, 0, paint);
}
///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
canvas.drawCircle(width / 2, height / 2,
radius + changeRadius, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
///< 采用默认的onMeasure看看
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
///< 自己进行相关测量
int defaultW = 12;
int defaultH = 12;
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
///< 在wrap_content的情况下默认长度为默认宽/高与背景图片对比后的最大值,假设背景图片是200*200,则显示200*200
int minWSize = Math.max(dp2px(context, defaultW), getSuggestedMinimumWidth());
int minHSize = Math.max(dp2px(context, defaultH), getSuggestedMinimumHeight());
///< wrap_content的specMode是AT_MOST模式,这种情况下宽/高等同于specSize
// 查表得这种情况下specSize等同于parentSize,也就是父容器当前剩余的大小
// 在wrap_content的情况下如果不特殊处理,效果等同martch_parent
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = minHSize;
setMeasuredDimension(minWSize, minHSize);
} else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
width = wSize;
height = hSize;
setMeasuredDimension(wSize, hSize);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
width = minWSize;
height = hSize;
setMeasuredDimension(minWSize, hSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
width = wSize;
height = minHSize;
setMeasuredDimension(wSize, minHSize);
}
///< 做一个兼容,如果半径超过了控件宽或者高
int minWH = width;
if (width > height) {
minWH = height;
}
if ((radius * 2) > minWH) {
radius = minWH / 2;
Log.e("attrs", "纠正一下 " + radius);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
///< 点击区域坐标范围
int minX = (width - radius * 2) / 2;
int maxX = width / 2 + radius;
int minY = (height - radius * 2) / 2;
int maxY = height / 2 + radius;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (x >= minX && x <= maxX &&
y >= minY && y <= maxY) {
bIsDownInRedRegion = true;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (bIsDownInRedRegion) {
bIsDownInRedRegion = false;
if (x >= minX && x <= maxX &&
y >= minY && y <= maxY) {
///< 抬手时我们就可以启动定时器进行绘制刷新了
Log.e("test", "红色区域点击了呀,sb");
if (null == timer) {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
///< Handler也行
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
updateDraw();
}
});
}
}, 0, 100);
} else {
timer.cancel();
timer = null;
}
}
}
break;
}
return true;
}
/**
* 刷新绘制+增量变化
*/
private void updateDraw() {
changeRadius = addFlag ? (changeRadius += STEP_RADIUS) : (changeRadius -= STEP_RADIUS);
if (changeRadius > 50) {
addFlag = false;
} else if (changeRadius < 0) {
addFlag = true;
}
invalidate();
}
/**
* dp转px
*
* @param dp
* @return
*/
public static int dp2px(Context context, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
}
或许不规范,或者说都不对。不过效果还是正确的啦。。先这样理解。我们先把自定义流程走一遍,然后再去接触更复杂的自定义的时候,相信肯定还能去理解和完善。
<?xml version="1.0" encoding="utf-8"?>
<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">
<!--android:background="@drawable/luffy"-->
<!--android:minWidth=""-->
<!--android:minHeight=""-->
<!--app:bgdrawable="@drawable/luffy"-->
<!--android:minWidth="12dp"-->
<me.heyclock.hl.customcopy.MyTextView01
style="@style/MyTextView01"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
app:ccolor="#F50808"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:radius="60dp" />
</android.support.constraint.ConstraintLayout>
image
下一篇onLayout,以及padding等....
心灵鸡汤:
拼搏如一汪清水,有源则灵,沉寂而终;拼搏如向日之花,有光则茁,无温而萎;拼搏如扬起之帆,顺风则行,无风则止;拼搏如穿石之滴,有恒则稳,无疾而终。
网友评论