自定义view大杂烩

标签: android  安卓

前天看了同事的自定义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 .上天注定的

7f0709bd9deb4acf946eec9e9084434b (1).jpeg

首先纠正一个误区。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的第三个三角的博客,在这里表示感谢.

		/**
         * Canvas.drawXXX() 方法,都是以左上角作为基准点的,而 drawText() 却是文字左下方
         * Canvas.drawText() 只能绘制单行的文字,而不能换行
         * 不能在换行符 \n 处换行
         * 需要绘制多行的文字可以使用StaticLayout
         */
        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);

        /**
         * 测量text的范围
         */
        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);

        /**
         * 测量text的宽度
         */
        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);
版权声明:本文为blackhopeless原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/blackhopeless/article/details/107351236

智能推荐

ActiveMQ学习4-ActiveMQ的安全机制和集群模式

ActiveMQ的安全机制和集群模式 20 ActiveMQ安全机制 20.1 Web 控制台安全 20.2 消息服务器Broker安全 21 ActiveMQ主从集群 21.1 使用集群的重要性 20.2 主从集群的方式 20.2.1 shared filesystem Master-Slave方式主从集群 20.2.2 shared database Master-Slave方式主从集群 20...

说说 Python Django 应用的基础目录结构

通过以下 django-admin 指令创建应用之后,就会生成应用的基础目录结构。 比如,我们建立了一个叫 ‘first’ 的应用,它的目录结构是这样的: 目录或文件 说明 最外层的 first/ 这是新应用的根目录,所有与该应用相关的内容都放在这里。 manage.py 用于管理 Django 项目的命令行工具。 里面一层的 first/ 目录 是一个...

Springboot整合rabbitMQ

依赖: 配置文件application.yml RabbitConfig 消息生产者RabbitProducer 消息消费者RabbitCustomer 通过Controller进行调用 启动项目后调用接口: 结果:...

Thread.join()方法的使用

如果一个线程A执行了thread.join()语句,代表当前线程A等待thread线程终止后才从thread.join()方法返回 并且这个方法具有超时特性,可以添加参数设置 输出结果: jdk中Thread.join()方法的源码(进行了部门调整)   每个线程终止的条件是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,  当线程终止时,会调用自身的no...

linux服务器部署jenkins笔记

安装jenkins参考文档:https://blog.csdn.net/tomatocc/article/details/83930714 1. 打开jenkins官网:https://jenkins.io/download/ 将war包下载到本地 **ps:**这里要注意的是要下载左边下方的war包,不要下载右边下面的war包。左边是稳定版本,右边是最新版本,建议大家使用稳定版本(我刚开始下载的...

猜你喜欢

k8s部署elasticsearch集群

百度营销大学     环境准备 我们使用的k8s和ceph环境见: https://blog.51cto.com/leejia/2495558 https://blog.51cto.com/leejia/2499684 ECK简介 Elastic Cloud on Kubernetes,这是一款基于 Kubernetes Operator 模式的新型编排产品,用户可使用该产品在...

saas-export项目-AdminLTE介绍与入门

AdminLTE介绍 (1)AdminLTE是什么? AdminLTE是一款建立在bootstrap和jquery之上的开源的模板主题工具 (2)AdminLTE有什么特点? 提供一系列响应的、可重复使用的组件, 并内置了多个模板页面 自适应多种屏幕分辨率,兼容PC和移动端 快速的创建一个响应式的Html5网站 AdminLTE 不但美观, 而且可以免去写很大CSS与JS的工作量 AdminLTE...

MyBatis中ResultMap结果集映射

用于解决属性名和字段名不一致的情况: resultMap 元素是 MyBatis 中最重要最强大的元素。...

编写一个shell

编写shell的过程: 1.从标准输入中读入一个字符串。 2.解析字符串 3.创建一个子进程的执行程序。 4.子进程程序替换。 5.父进程等待子进程退出。...

WEB自动化测试中Xpath定位方法

前言: Xpath是在XML文档中查找信息的一种语言,使用路径表达式来选取XML文档中的节点或节点集,由于XML与HTML结构类似(前者用于传输数据,后者用于显示数据),所以Xpath也常用于查找HTML文档中的节点或节点集。 一  路径表达式: 路径以“/”开始     表示找到满足该绝对路径的元素; 路径以//”开始  ...