我是靠谱客的博主 唠叨犀牛,最近开发中收集的这篇文章主要介绍自定义View和ViewGroup之TagLayout前言TagLayout如何实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

最近几个月终于有大把时间总结这两年来所学 2019.6.1

前言

上一篇自己写了一个模仿微信的SlideView,主要复习了自定义View方面的一些知识点。这几天有看到自定义ViewGroup相关的知识点,于是也打算拿一个简单的自定义ViewGroup来学习一下。

Github 地址

TagLayout

效果展示

下面这个是一个个人兴趣选择界面,里面主要是各种各样的Tag。下面我选取其中一部分,来实现。

我的实现

模仿的对象

使用方法

先看看怎么用

  • 在xml中
...
<android.support.constraint.ConstraintLayout
...>

    <com.cerkerli.library.view.TagLayout
        android:id="@+id/tag_layout"
        ...>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/material_design"
            android:text="@string/material_design"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rx_java"
            android:text="@string/rxjava"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/gradle"
            android:text="@string/gradle"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/android_studio"
            android:text="@string/android_studio"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/kotlin"
            android:text="@string/kotlin"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/flutter"
            android:text="@string/flutter"
            android:textSize="16sp"/>

        <com.cerkerli.library.view.TagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/more..."
            android:text="@string/more"
            android:textSize="16sp"/>
    </com.cerkerli.library.view.TagLayout>
</android.support.constraint.ConstraintLayout>
复制代码

在activity中

        TagLayout tagLayout = findViewById(R.id.tag_layout);
        tagLayout.setOnClickListener(new Listener.TagLayoutClickListener() {
            @Override
            public void onClick(int id, String text, boolean isSelected) {
                switch (id){
                    case R.id.material_design:
                    case R.id.rx_java:
                    case R.id.gradle:
                    case R.id.android_studio:
                    case R.id.kotlin:
                    case R.id.flutter:

                        Util.toast(text + " " + isSelected);
                    break;
                    default:break;
                }
            }
        });
复制代码

我们可以在activity中做响应的操作。 在Application中,做初始化。

    public void onCreate() {
        super.onCreate();
        Library.init(getApplicationContext());
    }
复制代码

--------------------------------我是分割线---------------------------------


如何实现

  • 简单介绍一下实现

TagView

TagView继承自TextView,在TextView的基础上添加了一个椭圆形的文本框。 重点方法有三个:onMeasure,onDraw,onTouchEvent,下面分别介绍一个每个方法。

onMeasure

onMeasure的主要功能是重新测量View的尺寸。我们知道在xml的配置中,layout_width,layout_height有三种值,分别是wrap_content,match_parent,exactly「10dp 30dp」,我在onMeasure中做了下面的处理:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //测量文字长度
        getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), textRect);
        widthSize = textRect.right - textRect.left;
        //测量文字的推荐高度
        //这个问题被卡的比较久,之前一个用的是 textRect的top和bottom
        Paint.FontMetrics fontMetrics = getPaint().getFontMetrics();
        heightSize = (int)(fontMetrics.bottom - fontMetrics.top);
        //重新测量长和宽
        setMeasuredDimension(widthSize + heightSize * 2,
                heightSize + heightSize);
    }
复制代码

这里可以看到,我强制重新测量了文字的width和height,改成了wrap_content。这意味着,整个文本框的大小都由文字的大小决定,而不是 layout_width,layout_height 这两个值,就是说,不管你把layout_width,layout_height写成多少dp,最后整个文本框的大小都由 textSize参数决定。

这里的重要知识点是文字的测量,想要具体学习的话,推荐看看Hencoder课程

onDraw

onDraw的功能比较单一了,绘制一个文本框,以及对文字做一个偏移,保证文字居中对齐于文本框。实现的效果像下面这样子。

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRoundRect(CIRCLE_WIDTH, CIRCLE_WIDTH,
                getWidth() - (CIRCLE_WIDTH << 1),
                getHeight() - (CIRCLE_WIDTH << 1), circleRadius, circleRadius, mPaint);
        canvas.save();
        canvas.translate(heightSize,   heightSize >> 1);
        super.onDraw(canvas);
        canvas.restore();
    }
复制代码

onTouchEvent

onTouchEvent 实现点击事件,由于这个点击事件比较简单,所以就没有用框架,自己简单实现了一下。 点击事件重点是两步:捕获事件,消费事件 捕获事件在MotionEvent.ACTION_DOWN中返回true即可。 消费事件就是在其他动作里面做一些操作。 代码如下:

    public boolean onTouchEvent(MotionEvent event) {
        //捕获事件
        if(event.getActionMasked() == MotionEvent.ACTION_DOWN){
            return true;
        }
        //消费事件
        if(event.getActionMasked() == MotionEvent.ACTION_UP){
            //未点击-->点击
            if(!isSelected){
                isSelected = true;
                setPaintSelected(true);
                
            }else {
            //点击-->未点击
                isSelected = false;
                setPaintSelected(false);
            }
            //监听回调
            if(clickListener != null){
                clickListener.onClick(getText().toString(),isSelected);
            }
            invalidate();
        }
        return false;
    }
复制代码
  • TagView的部分是比较简单的,我在实现的时候,主要是遇到了文字测量的问题,花了不少时间,其他的都很基础。

TagLayout

TagLayout分为两部分,onMeasure和onLayout

onLayout

onLayout 比较容易,分别绘制每一个子View,然后将onMeasure中存储的数据读取出来就可以了,代码如下:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for(int i = 0;i < getChildCount();i++){
            final TagView child = (TagView)getChildAt(i);
            Rect childBond = mChildBounds.get(i);
            child.layout(childBond.left,childBond.top,childBond.right,childBond.bottom);

            //设置点击监听
            child.setOnClickListener(new Listener.TagViewClickListener() {
                @Override
                public void onClick(String text,boolean isSelected) {
                    if(clickListener != null){
                        clickListener.onClick(child.getId(),text,isSelected);
                    }
                }
            });
        }
    }
复制代码

监听的部分是监听子View中的点击事件。

onMeasure

  • onMeasure的实现非常非常麻烦,麻烦在于需要挨个测量子View的尺寸,以及计算自己的尺寸,还有各种换行的情况需要考虑,但是整体的逻辑是很简单的,先贴一下代码:
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //当前行高
        int LineWidthUsed = 0;
        int LineHeightUsed = 0;
        //总行高
        int HeightUsed = 0;
        int WidthUsed = 0;
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        for(int i = 0;i < getChildCount();i++){
            //获取子 View 以及布局子 View
            View child = getChildAt(i);
            measureChildWithMargins(child,widthMeasureSpec,LineWidthUsed,heightMeasureSpec,HeightUsed);
            if(LineWidthUsed + child.getMeasuredWidth() + Constants.WIDTH_NAP > widthSize &&
                    widthMode != MeasureSpec.UNSPECIFIED){
                //是否需要换行
                WidthUsed = LineWidthUsed;
                LineWidthUsed = 0;
                HeightUsed += LineHeightUsed;
                HeightUsed += Constants.HEIGHT_NAP;
                measureChildWithMargins(child,widthMeasureSpec,LineWidthUsed,heightMeasureSpec,HeightUsed);

                LLog.d(TAG,"换行 at " + i + " WidthUsed " + WidthUsed + " HeightUsed " + HeightUsed);
            }
            //数据写入List中 供Layout使用
            mRect = new Rect();
            mRect.set(LineWidthUsed,HeightUsed,LineWidthUsed + child.getMeasuredWidth(),HeightUsed + child.getMeasuredHeight());
            mChildBounds.add(mRect);

            LLog.d(TAG,"i == "+i);
            LLog.d(TAG,mRect);

            //数据增加
            LineWidthUsed += child.getMeasuredWidth();
            LineWidthUsed += Constants.WIDTH_NAP;
            LineHeightUsed = Math.max(LineHeightUsed,child.getMeasuredHeight());
        }
        HeightUsed+=LineHeightUsed;
        setMeasuredDimension(widthSize,  HeightUsed);

        LLog.d(TAG,"setMeasuredDimension "+ widthSize + ":" + HeightUsed);
    }
复制代码

这个整体逻辑其实很简单,就是挨个去测量每个子View的尺寸,然后记录下来,最后把自己的总尺寸也记录下来。 这里有三个很关键的方法:measureChildWithMarginschild.getMeasuredWidth() child.getMeasuredHeight()setMeasuredDimension(...);

方法1. measureChildWithMargins:这个方法是获取LayoutParams,然后调用child.measure(...),我可以贴一下他的源码,

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
复制代码

从源码可以看出,这个其实是一个集成好的方法,比较方便我们写的时候,就不用在一一判断三种值了(上面说到了wrap_content,match_parent,exactly「10dp 30dp」

方法2. child.getMeasuredWidth() child.getMeasuredHeight():这两个是获取子View的宽高,方便我们计算子View,然后通过子View计算自己的宽高。 方法3. setMeasuredDimension(...);:这个方法就是确定自己的宽高,和View的中用法一模一样。

— 这里就简单介绍一下TagLayout的实现,需要有一些基础才能看懂,所以没有基础的,建议看看Hencoder课程,这个我目前见过讲自定义View最完善的课程,没有之一。

TagLayout GitHub详细地址

再另外,以上都是自己平时所学整理,如果有错误,欢迎留言或者添加微信批评指出,一起学习,共同进步,爱生活,爱技术

最后

以上就是唠叨犀牛为你收集整理的自定义View和ViewGroup之TagLayout前言TagLayout如何实现的全部内容,希望文章能够帮你解决自定义View和ViewGroup之TagLayout前言TagLayout如何实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部