美文网首页安卓开发
安卓开发笔记——自定义控件学习小结

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

作者: 一直下沉 | 来源:发表于2020-02-09 16:59 被阅读0次

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

    需求:服务端传过来的数据长这个样子:
    这是一个<important>甲方爸爸</important>特别<important>强调</important>的需求
    在TextView中要显示成这个样子:
    这是一个\color{#ff8200}{甲方爸爸}特别\color{#ff8200}{强调}的需求

    就这个需求

    下面就是我的进化之路:

    零、不使用自定义控件

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

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

    纯属一个方法,于是在那个页面里,满眼望去全是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("<mportant>").length < 2) {
                setText(text);
            } else {
                text = text.replace("/mportant", "/font></html").replace("em", "html><font color=\"" + color + "\"");
                setText(Html.fromHtml(text));
            }
         }
    }
    

    补充:为什么继承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("<important>").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();
    }
    

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

    没完,其实还有第三种

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

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

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

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
    
        <TextView
            android:id="@+id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/text9"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    而控件代码如下:

    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("<important>").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);
            }
        }
    }
    

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

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


    补充说明:

    如果:这段<important>文字</important>是<important>这个<important>样子的
    我这里显示:这段\color{#ff8200}{文字}\color{#ff8200}{这个}样子的
    但按照需求应该:这段\color{#ff8200}{文字}是<important>这个<important>样子的

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


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

    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("<important>").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);
        }
    }
    

    相关文章

      网友评论

        本文标题:安卓开发笔记——自定义控件学习小结

        本文链接:https://www.haomeiwen.com/subject/oxhnxhtx.html