文章目录
- 自定义控件
- 自定义控件:第一步
- 自定义控件:第二步
- 自定义控件:总结
- 绘制view视图
- 绘制大小:MeasureSpec
- 测量模式
- 开始实践
- 设置view大小
- 如何自定义view
- 1.demo
- 2.添加wrap_content的支持
- 3.添加padding的支持[margin是由父布局控制的]
- 4.为了方便配置,使用自定义属性
- 总结:
- view的测量
- 1 view.post(runnalbe)
- 2.进行测量
- 案例:
- api大法好呀
前天看了同事的自定义view博客.也对view的绘制也做一个总结. 高手请指教,小白可以借鉴.
自定义控件
自定义控件:第一步
为了将相同的布局文件在不同的地方使用,不用ctrl+c,ctrl+v,修改时再到处ctrl+f.所以将这个布局文件抽取出来,然后在需要使用的地方引用.
创建文件: layout_two_button.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:text="被观察" />
<Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:text="观察" />
</LinearLayout>
在需要的地方引用就可以了
<include layout="@layout/layout_two_button" />
自定义控件:第二步
可是这些布局的点击函数大多相同,为了不在java文件中再设置相同的点击事件,
而xml只是布局文件,不是对象是没有点击事件的.继续将这些抽取出来的文件,变成类对象.
public class ButtonLineLayout extends LinearLayout {
public ButtonLineLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
View inflate = LayoutInflater.from(context).inflate(R.layout.layout_two_button, this);
inflate.findViewById(R.id.button1).setOnClickListener(v -> Toast.makeText(context, "button1", Toast.LENGTH_SHORT).show());
}
}
将创建的类对象在需要的文件中引用
<com.ui.material.ButtonLineLayout android:layout_width="match_parent" android:layout_height="wrap_content" />
自定义控件:总结
说到底,这只不过是一个特殊的lineLayout,里面添加了我们视图,并赋予了点击事件而已。
绘制view视图
小菜开胃,开始正餐。
绘制大小:MeasureSpec
首先来认识一个变量MeaureSpec. MeasureSpec 是一个32位的Int值。
MeasureSpec的前2位是表示测量模式,后面30位是视图的大小。
测量模式
- exactly : 精确大小。
- at_most: 限定了最大值。
- unspecified : 不限制大小。你想多大就多大。一般用不到,忘记它吧。它是你得不到的人。
子view MeasureSpec 是由 父布局的 MeasureSpec 和 自身 布局属性 决定的.
测量模式和wrapCont的关系简单的记:
- 指定大小 = exactly
- wrapContent = at_most
- matchParent 视情况而定
Exactly下的matchParent就是Exactly的.
at_most下的matchParent就是at_most .上天注定的
首先纠正一个误区。textVeiw中的wrap_content可以实现自适应大小,是人家做了代码逻辑处理。
不是把你的view设置了wrap_content也不用管他的大小了。。
它不会孙猴子的铁棒可以你想多大就可以多大。
在源码中view的measure->onMeasure->getDeafultSize方法中可以看到at_most,exactly 没有区别。
哎,不给你看,来打我呀。
开始实践
设置view大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(23, 23);
}else if(widthMode == MeasureSpec.AT_MOST ){
setMeasuredDimension(23,heightSize);
}else if(heightMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpec,23);
}
}
总结 :
MeasureSpec.getMode 获取模式
MeasureSpec.getSize 获取大小
setMeasuredDimension 设置view大小
如何自定义view
- 继承TextView 对view实现功能的扩展
- 继承Linelayout对viewGroup实现扩展
- 继承View
- 继承viewGroup
继承系统控件只是对功能进行扩展,不做过多描述.
下面看如何继承view实现绘制.
1.demo
public class CirecleView extends View {
private int mColor = Color.RED;
private Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
public CirecleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(mColor);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2;
canvas.drawCircle(width / 2, height / 2, radius, mPaint);
}
}
2.添加wrap_content的支持
public class CirecleView extends View {
******
~~~~~~
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//由一个是精确模式,就取精确模式的值.
if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
int size = Math.min(widthSize, heightSize);
setMeasuredDimension(size, size);
} else {
//否则使用默认值
int defaultSize = 400;
setMeasuredDimension(defaultSize, defaultSize);
}
}
}
3.添加padding的支持[margin是由父布局控制的]
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(mColor);
//首先明确一点,视图的宽高已经固定,padding越大,半径越小
int width = getWidth() - (getPaddingLeft() + getPaddingRight()) ;
int height = getHeight() - (getPaddingTop() + getPaddingBottom()) ;
int radius = Math.min(width, height) / 2;
//然后左,上决定位置
canvas.drawCircle(width / 2 + getPaddingLeft(), height / 2 + getPaddingTop(), radius, mPaint);
}
4.为了方便配置,使用自定义属性
attrs.xml中设置 类,属性,类型
<?xml version="1.0" encoding="utf-8"?>
<resources>
//类
<declare-styleable name="CirecleView">
//属性 //类型
<attr name="circle_color" format="color"></attr>
</declare-styleable>
</resources>
public CirecleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取类下所有的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CirecleView);
mColor = typedArray.getColor(R.styleable.CirecleView_circle_color, 0xff0000);
typedArray.recycle();
}
总结:
onMeasure 中添加wrapContent的判断,显示大小
onDraw中添加padding的支持
再提供一些自定义的属性.一个简单的自定义view就完成了
view的测量
我们可以通过setMeasuredDimension来设置view的大小,但是其他view的大小我们如何得知呢.
getWidth()?getMeasureWidth()?
view的绘制和activity中生命周期没有关系。
onResume时,acitivity都可以可见互动了,你还没绘制好,玩我呢。
通常做法:
1 view.post(runnalbe)
view.post(() -> {
view.getMeasuredHeight();
});
补充说明:
getMeasuredHeight 是测量大小,
getHight是布局大小。
一般而言两者没有区别,但是可以有,只不过没啥意义. 【ps:我的面试题,当时一脸懵逼】
重写Layout方法后,测量宽度getMeasuredHeight 会比getHight小100.
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+100, b+100);
}
2.进行测量
如果是matchParent模式:
在绘制view的时候还不知道父布局的大小,所以无解
如果是wrapContent模式
//创建一个MeaureSpec:告知view的最大值和测量模式
int heightSpec = weidthSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST)
//给爷去测量
view.measure(weidthSpec,heightSpec)
//获取测量的数值
view.getMeasuredWidth();
ps:需要准确的告知测量模式
案例:
Log.d("test",cire.measuredHeight.toString()) // 0
val widthSpec = MeasureSpec.makeMeasureSpec(resources.getDimension(R.dimen.fab_margin_50).toInt(), MeasureSpec.EXACTLY)
val heightSpec= MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST)
cire.measure(widthSpec,heightSpec)
Log.d("test",cire.measuredHeight.toString()) // 正确结果131
Log.d("test",cire.height.toString()) // 0
结果发现getHeight测量后还是0,getMeasureHeight就已经有值了
上面刚说getMeasuredHeight 和 getHight没有区别 ,结果就啪啪的打脸.我不要面子的嘛.-.-
就画一个圈,就说会绘制view了.你在敷衍谁呢,好歹告诉我怎么画一个框呀.好满足你.api大法上!
api大法好呀
借鉴T9的第三个三角的博客,在这里表示感谢.
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTypeface(Typeface.SANS_SERIF);//字体
mPaint.setFakeBoldText(true);//粗体
mPaint.setStrikeThruText(true);//删除线
mPaint.setUnderlineText(true); //下划线
mPaint.setTextSkewX(-0.4f);//倾斜
// mPaint.setLetterSpacing(0.8f);//字符间距
// mPaint.setTextAlign(Paint.Align.RIGHT);//对齐
mPaint.setStrokeWidth(5);
mPaint.setTextSize(200);
canvas.drawText("practice",20,200,mPaint);
Rect bounds = new Rect();
mPaint.getTextBounds("practice",0,"practice".length(),bounds);
bounds.left += 20;
bounds.right += 20;
bounds.top += 200;
bounds.bottom += 200;
canvas.drawRect(bounds,mPaint);
float textWidth = mPaint.measureText("practice");
// bounds.width();
canvas.drawLine(20,200, textWidth += 20,200,mPaint);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float height1 = fontMetrics.descent - fontMetrics.ascent;
float height2 = fontMetrics.bottom - fontMetrics.top;
canvas.clipRect(100,100,400,400);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_name);
canvas.drawBitmap(bitmap,100,100,mPaint);
Path path = new Path();
path.addCircle(500,500,400, Path.Direction.CCW);
canvas.clipPath(path);
canvas.translate(300,100);
canvas.rotate(200);
canvas.scale(1.5f,0.8f,100,100);
canvas.drawCircle(20, 20, 4, mPaint);
canvas.drawRect(20,0,20,90,mPaint);