欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

自定义view大杂烩

程序员文章站 2022-07-03 18:21:13
文章目录自定义控件自定义控件:第一步自定义控件:第二步自定义控件:总结绘制view视图绘制大小:MeasureSpec测量模式开始实践设置view大小如何自定义view1.demo2.添加wrap_content的支持3.添加padding的支持[margin是由父布局控制的]4.为了方便配置,使用自定义属性总结:view的测量1 view.post(runnalbe)2.进行测量案例:api大法好呀前天看了同事的自定义view博客.也对view的绘制也做一个总结. 高手请指教,小白可以借鉴.自定义控件...

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

自定义view大杂烩

首先纠正一个误区。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);

本文地址:https://blog.csdn.net/blackhopeless/article/details/107351236

相关标签: android