前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android笔记】 CustomView

【Android笔记】 CustomView

原创
作者头像
易寒
发布2021-12-12 14:02:23
3530
发布2021-12-12 14:02:23
举报
文章被收录于专栏:Android知识Android知识

View工作流程 View工作流程主要指measure、layout、draw这三个流程,即测量、布局和绘制,其中measure确定View的自身的宽高,layout确定View在父容器放置的位置,draw将View绘制到屏幕上。

measure

为了更好理解measure过程,先了解MeasureSpec,MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize(这句话不知道几个意思)。SpecMode指的测量模式,SpecSize指在某种测量模式下规格大小。

SpecMode有三种:

EXACTLY

精确值模式,父容器已经检测出View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值。它对应代码LayoutParams(控件layout_width属性和layout_height属性)中match_parent和具体数值。

AT_MOST

最大值模式,父容器指定了一个可用大小值,只要不超过父容器允许最大尺寸即可。它对应代码LayoutParams(控件layout_width属性和layout_height属性)中wrap_content。

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,如ListView。

自定义控件不重写onMeasure(),View类默认只支持EXACTLY模式,如果让View支持wrap_content属性,必须重写onMeasure()来指定wrap_content大小。代码如下:

代码语言:javascript
复制
 private int mWidth = 500, mHeight = 450;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int finalWidth = mWidth > widthSpecSize ? mWidth : widthSpecSize;
int finalheight = mHeight > heightSpecSize ? mHeight : heightSpecSize;
//view支持wrap_content
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(finalWidth, finalheight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(finalWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, finalheight);
}
}

setMeasuredDimension方法会设置View宽/高的测量值

layout

layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,他在onLayout会遍历所有子元素并调用其layout方法。layout方法大致流程:首先通过setFrame设定View四个顶点位置,View四个顶点一旦确定,那么它在父容器中位置也就确定了。

draw

重写onDraw()方法,并在Canvas对象上绘制所需要的图形,简而言之,有画笔和画布就可: Android 画笔Paint Android 画布CanvasAndroid之画笔Paint和画布Canvas及实例练习圆角、刮刮卡、圆形头像、倒影效果

自定义attrs属性

attrs.xml

values目录下创建attrs.xml

代码语言:javascript
复制
<resources>
<!--<declare-styleable>声明自定义属性-->
<declare-styleable name="EmptyView">
<!--<attr>声明具体自定义属性-->
<attr name="empty_text" format="string"/>
<attr name="empty_text_color" format="color"/>
<attr name="empty_image" format="reference"/>
</declare-styleable>
</resources>

这里format的color指的颜色。其他:reference指资源ID;dimension指尺寸;string、integer、boolean指基本数据类型。也可以用“|”来分隔不同的属性。

完整代码

代码语言:javascript
复制
public class EmptyView extends View {
private int mWidth = 500, mHeight = 450;
private Paint mPaint;
private String emptyText;
private Drawable emptyImage;
private Bitmap bitmap;
private Rect textRect;
private Context context;

public EmptyView(Context context) {
super(context);
this.context = context;
initView();
}

public EmptyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EmptyView);
emptyImage = typedArray.getDrawable(R.styleable.EmptyView_empty_image);
emptyText = typedArray.getString(R.styleable.EmptyView_empty_text);
typedArray.recycle();

initView();
}

private void initView() {

BitmapDrawable bitmapDrawable = (BitmapDrawable) emptyImage;
bitmap = bitmapDrawable.getBitmap();

mPaint = new Paint();
mPaint.setColor(Color.GRAY);
mPaint.setTextSize(40f);
mPaint.setAntiAlias(true);
textRect = new Rect();
mPaint.getTextBounds(emptyText, 0, emptyText.length(), textRect);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int finalWidth = mWidth > widthSpecSize ? mWidth : widthSpecSize;
int finalheight = mHeight > heightSpecSize ? mHeight : heightSpecSize;
//view支持wrap_content
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(finalWidth, finalheight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(finalWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, finalheight);
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//支持padding,不然padding属性无效
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
LogUtil.d("paddingLeft=" + paddingLeft + ",paddingTop=" + paddingTop + ",paddingRight=" + paddingRight + ",paddingBottom=" + paddingBottom);
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
canvas.drawText(emptyText, (float) ((width - textRect.width()) * 0.5), (float) (height * 0.5), mPaint);
canvas.drawBitmap(bitmap, (float) ((width - bitmap.getWidth()) * 0.5), (float) (height * 0.5 - bitmap.getHeight() - 100), mPaint);
}

public void setEmptyImage(int id) {
emptyImage = ContextCompat.getDrawable(context, id);
BitmapDrawable bitmapDrawable = (BitmapDrawable) emptyImage;
bitmap = bitmapDrawable.getBitmap();
//requestLayout();
invalidate();
}

public void setEmptyText(String text) {
emptyText = text;
mPaint.getTextBounds(emptyText, 0, emptyText.length(), textRect);
invalidate();
}
}

xml引用

代码语言:javascript
复制
<com.wuxiaolong.androidsamples.customview.EmptyVie
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_margin="10dp"
android:padding="10dp"
app:empty_image="@mipmap/empty_image01"
app:empty_text="空信息提示语!"/>

布局文件添加schemas声明: xmlns:circle="http://schemas.android.com/apk/res-auto" ,这里circle是自定义前缀,名字随便取。但与下面CustomView中自定义属性circle:circle_color=""前缀一致。另外也可以声明 xmlns:circle="http://schemas.android.com/apk/res/包名" ,效果是一样的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • measure
  • layout
  • draw
  • attrs.xml
  • 完整代码
  • xml引用
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档