安卓开发笔记——自定义控件学习小结

在很多安卓岗位的职位描述上,都会提到一个“自定义控件”。这个东西上手其实并不难,但真想做好自定义控件,要会的东西还挺多。下面我就分享个简单的例子,给自己,也给需要的人。

需求:服务端传过来的数据长这个样子:
这是一个甲方爸爸特别强调的需求
在TextView中要显示成这个样子:
这是一个特别的需求

就这个需求

下面就是我的进化之路:

零、不使用自定义控件

其实就是简单的文字替换,Android中有一个Spannable

public static Spanned important(String text, String color) {
   if ( text.split("").length < 2) return new SpannableString(important);
   text = text.replace("/ important", "/font>

纯属一个方法,于是在那个页面里,满眼望去全是important:

tvName.setText(important(object.getString("name")));
tvJob.setText(important(object.getString("job"))); 
tvDevice.setText(important(object.getString("device")));

然后就引出了第一种自定义控件

一、扩展控件

最简单的,是基于现有控件进行控件扩展。既然方法已经写好了,继承原来的TextView写一个新的控件,把setText()方法重写一下就好啦:

public class ImportantTextView extends AppCompatTextView {
    public ImportantTextView(Context context) {
        super(context);
    }
    public ImportantTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    public ImportantTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    public void setText(String text) {
        setImportantText(text, "#ff8200");
    }
    public void setImportantText(String text, String color) {
        if (text.split("").length < 2) {
            setText(text);
        } else {
            text = text.replace("/mportant", "/font>

补充:为什么继承AppCompatTextView而不是TextView?
AppCompatTextView是在API level 23引入的,继承自TextView,它是Android标准TextView的增强,特点就是可以自适应字体宽度大小变化。有一点要提的是,在android官方文档中,xml里写的传统TextView已经被编译器替换成AppCompatTextView了,不需要开发者再去手动替换。
另外,如果自定义控件上面继承TextView,会报错的。。。

但是,就这么写一个控件实在是拿不出手,其实就相当于把外面的方法放到了控件里,太没技术含量了

二、重新绘制控件

先重写onLayout():

略,因为没写~

再重写onMeasure():

略,因为也没写~

上面这两个方法,前者用来确定控件在父控件的位置,后者用来测量控件的宽高大小。
在这里我只用到了onDraw()方法:

@Override
protected void onDraw(Canvas canvas) {
    if (text == null || text.isEmpty())
        return; // 如果没有输入文字,也就没有意义画出控件了
    if (text.split("").length < 2)
        canvas.drawText(text, 0, getHeight(), mPaint); // 如果没有重点标签或者标签只出了一个,也没有必要进行加工了
    else {
        String[] t = text.replace("/important", "")
                        .replace("important", "")
                        .split("<>");
        float x = 0.0f;
        for (int i = 0; i < t.length; i++) {
        int textWidth = 0;
        canvas.save();
        if (i % 2 == 0) {
            canvas.drawText(t[i], x, getY(), mPaint);
            textWidth = getTextWidth(mPaint, t[i]);
        } else {
            canvas.drawText(t[i], x, getY(), mEMPaint);
            textWidth = getTextWidth(mEMPaint, t[i]);
        }
        x = x + textWidth;
        canvas.restore();
        }
     }
    requestLayout();
}

在绘制的时候就直接把颜色涂到上面,这样比之前的更显技术含量~

没完,其实还有第三种

三、基于控件容器的自定义控件

最简单,效率一般,可读性一般,功能性简单,非通用方法

在这个需求要怎么写呢?
先要有一个布局文件:




    

    

    

    

    

    

    

    

    


而控件代码如下:

public ImportantTextView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    LayoutInflater.from(context).inflate(R.layout.layout_important, this, true); 
    tvTexts = new ArrayList<>();
    tvTexts.add((TextView) findViewById(R.id.text1));
    tvTexts.add((TextView) findViewById(R.id.text2));
    tvTexts.add((TextView) findViewById(R.id.text3));
    tvTexts.add((TextView) findViewById(R.id.text4));
    tvTexts.add((TextView) findViewById(R.id.text5));
    tvTexts.add((TextView) findViewById(R.id.text6));
    tvTexts.add((TextView) findViewById(R.id.text7));
}

public void setText(String text) {
    if (text == null || text.isEmpty())
        return;
    if (text.split("").length < 2) {
        tvTexts.get(0).setText(text);
    } else {
        String[] t = text.replace("/important", "").replace("important", "").split("<>");
        int i = 0;
        while (i < t.length && i < tvTexts.size() - 1) {
            if (i % 2 != 0)
            tvTexts.get(i).setTextColor(0xffff8200);
            tvTexts.get(i).setText(t[i]);
            i++;
        }
        if (i >= tvTexts.size() - 1) {
            StringBuilder last = new StringBuilder();
            for (; i < t.length; i++) {
                last.append(t[i]);
            }
            tvTexts.get(tvTexts.size() - 1).setText(last);
        }
    }
}

简单粗暴,就是获得控件,给控件赋值。这种自定义控件可读性极强,但复用性极差,其实就是把很多原生空间整合到了一个控件容器,然后在项目中以整体形式出现。

这个控件不适用于这个需求,但并不代表这个方法没有用,通常这种做法用在列表控件或者重复性的布局上。


补充说明:

如果:这段文字这个样子的
我这里显示:这段是样子的
但按照需求应该:这段是这个样子的

这个是不太符合需求的,不过这个是线上项目,服务端的大哥指着房顶中央空调跟我保证过不会有这种情况,所以暂且忽略掉。
而且我也没想好如果真是这样该怎么处理,希望看到的能回贴帮我想一下,谢谢~


下面贴出第二套方案的完整代码:

package com.myprj.important.ImportantTextView;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;

import com.myprj.important.R;

public class ImportantTextView2 extends AppCompatTextView {

    private Paint mPaint, mImportantPaint;
    private String text;

    public ImportantTextView2(Context context) {
        this(context, null);
    }

    public ImportantTextView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ImportantTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context, attrs);
    }

    private void initPaint(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.importantTextView2);
        int color = array.getColor(R.styleable.ImportantTextView2_color, getTextColors().getDefaultColor());
        int importantColor = array.getColor(R.styleable.ImportantTextView2_importantColor, 0xffff8200);
        mPaint = getPaintByColor(color);
        mImportantPaint = getPaintByColor(importantColor);
        array.recycle();
    }

    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setTextSize(getTextSize());
        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (text == null || text.isEmpty())
            return;
        if (text.split("").length < 2)
            canvas.drawText(text, 0, getHeight(), mPaint);
        else {
            String[] t = text.replace("/important", "").replace("important", "").split("<>");
            float x = 0.0f;
            for (int i = 0; i < t.length; i++) {
                int textWidth = 0;
                canvas.save();
                if (i % 2 == 0) {
                    canvas.drawText(t[i], x, getY(), mPaint);
                    textWidth = getTextWidth(mPaint, t[i]);
                } else {
                    canvas.drawText(t[i], x, getY(), mImportantPaint);
                    textWidth = getTextWidth(mImportantPaint, t[i]);
                }
                x = x + textWidth;
                canvas.restore();
            }
        }
        requestLayout();
    }

    public static int getTextWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet;
    }

    public void setText(String text) {
        this.text = text;
        super.setText(text);
    }
}

你可能感兴趣的:(安卓开发笔记——自定义控件学习小结)