ShapedTextView探索与实现

2019-09-25  本文已影响0人  Brian512

项目中大部分场景的按钮是使用的TextView,然后设置shape作为背景,如果需要有点击效果反馈,则在drawable中使用selector。
这一套标准用法没啥毛病,唯一大缺点的就是繁琐。当然,如果项目的UI比较规范,定义几个常用的,复用起来也会很顺手。
但对于UI设计不规范的项目,写一堆的selector+shape还是很恶心的。

想法:shape的几个属性比较固定,就圆角和背景色等几个。能不能在xml写TextView时直接把这几个属性也传进去,这样就不用再去添加shape.xml了?

如果只是传几个属性,还是比较容易的,关键是如何把设置的参数使用起来。
第一思路:ShapeDrawable

float radius = DensityUtil.dip2px(this, 10);
float[] outerR = new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
RoundRectShape rr = new RoundRectShape(outerR, null, null);
ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(0xff333333);
drawable.getPaint().setStyle(Paint.Style.STROKE);
drawable.getPaint().setStrokeWidth(2);
code.setBackgroundDrawable(drawable);

看看drawable.xml加载的源码,发现shape加载出来的不是ShapeDrawable,而是GradientDrawable。

float radius = DensityUtil.dip2px(this, 10);
GradientDrawable drawable = new GradientDrawable();
drawable.setCornerRadius(radius);
drawable.setStroke(2, 0xff333333);
code.setBackgroundDrawable(drawable);

方案有了,就开干:

    <declare-styleable name="CompatTextView">
        <attr name="backgroundType">
            <enum name="solid" value="0" />
            <enum name="stroke" value="1" />
            <!--<enum name="gradient" value="2" />-->
        </attr>
        <attr name="solidColor" format="color|reference"/>
        <attr name="strokeColor" format="color|reference"/>
        <attr name="strokeWidth" format="dimension|reference"/>
        <attr name="radius" format="dimension|reference"/>
    </declare-styleable>
public class CompatTextView extends AppCompatTextView {

    public static final int TYPE_SOLID = 0;
    public static final int TYPE_STROKE = 1;
    public static final int TYPE_GRADIENT = 2;

    public CompatTextView(Context context) {
        this(context, null, 0);
    }
    public CompatTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        if (attrs != null) {
            initAttrs(context, attrs);
        }
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
        int bgType = typedArray.getInt(R.styleable.CompatTextView_backgroundType, -1);
        if (0 <= bgType && bgType <= 2) {
            float radius = typedArray.getDimension(R.styleable.CompatTextView_radius, 0);
            GradientDrawable drawable = new GradientDrawable();
            if (bgType == TYPE_SOLID) {
                int solidColor = typedArray.getInt(R.styleable.CompatTextView_solidColor, 0);
                drawable.setColor(solidColor);
            } else if (bgType == TYPE_STROKE) {
                int strokeWidth = (int) typedArray.getDimension(R.styleable.CompatTextView_strokeWidth, 0);
                int strokeColor = typedArray.getInt(R.styleable.CompatTextView_strokeColor, 0);
                drawable.setStroke(strokeWidth, strokeColor);
            }
            drawable.setCornerRadius(radius);
            setBackgroundDrawable(drawable);
        }
        typedArray.recycle();
    }
}

使用

    <com.brian.views.CompatTextView
        android:id="@+id/btn_logout"
        android:layout_width="288dp"
        android:layout_height="48dp"
        android:text="退出登录"
        android:textColor="#ffffffff"
        android:textSize="14sp"
        android:textStyle="bold"
        android:gravity="center"
        app:backgroundType="solid"
        app:solidColor="@color/popi_dark_btn_bg"
        app:radius="24dp"
        />

可以看到,使用时只添加了三个自定义属性,这样就省去了定义一个shape,效率提升,性能也稍微好一点(不需要加载drawable.xml);

如果需要添加点击态,也很简单,加一个属性,然后使用ColorStateList搞定:

 int[][] stateList = new int[][]{
         new int[]{android.R.attr.state_pressed},
         new int[]{}
 };
 int[] stateColorList = new int[]{
         pressedColor,
         typedArray.getInt(R.styleable.CompatTextView_solidColor, 0),
 };
 ColorStateList colorStateList = new ColorStateList(stateList, stateColorList);
 drawable.setColor(colorStateList);

现在Android版本也更新较快,目前Android4.x及以下的设备持有量较少,是时候普及 Android5.0引入的Ripple点击态了。
第一想法就是看看有没有RippleDrawable,搜索源码发现还真有这个类。
有就好办了,看api,然后CV一下就差不多完工了:

public class CompatTextView extends AppCompatTextView {

    public static final int TYPE_SOLID = 0;
    public static final int TYPE_STROKE = 1;
    public static final int TYPE_GRADIENT = 2;

    public CompatTextView(Context context) {
        this(context, null, 0);
    }
    public CompatTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        if (attrs != null) {
            initAttrs(context, attrs);
        }
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
        int bgType = typedArray.getInt(R.styleable.CompatTextView_backgroundType, -1);
        if (0 <= bgType && bgType <= 2) {
            float radius = typedArray.getDimension(R.styleable.CompatTextView_radius, 0);
            if (bgType == TYPE_SOLID) {
                int solidColor = typedArray.getInt(R.styleable.CompatTextView_solidColor, 0);
                GradientDrawable drawable = new GradientDrawable();
                drawable.setColor(solidColor);
                drawable.setCornerRadius(radius);

                int rippleColor = typedArray.getInt(R.styleable.CompatTextView_rippleColor, 0);
                if (rippleColor != 0) {
                    int[][] stateList = new int[][]{
                            new int[]{android.R.attr.state_pressed},
                            new int[]{}
                    };
                    int[] stateColorList = new int[]{
                            rippleColor,
                            solidColor,
                    };
                    ColorStateList colorStateList = new ColorStateList(stateList, stateColorList);
                    RippleDrawable rippleDrawable = new RippleDrawable(colorStateList, drawable, null);
                    setBackgroundDrawable(rippleDrawable);
                } else {
                    setBackgroundDrawable(drawable);
                }

            } else if (bgType == TYPE_STROKE) {
                int strokeWidth = (int) typedArray.getDimension(R.styleable.CompatTextView_strokeWidth, 0);
                int strokeColor = typedArray.getInt(R.styleable.CompatTextView_strokeColor, 0);
                GradientDrawable drawable = new GradientDrawable();
                drawable.setStroke(strokeWidth, strokeColor);
                drawable.setCornerRadius(radius);
                setBackgroundDrawable(drawable);
            }
        }
        typedArray.recycle();
    }
}
    <declare-styleable name="CompatTextView">
        <attr name="backgroundType">
            <enum name="solid" value="0" />
            <enum name="stroke" value="1" />
            <!--<enum name="gradient" value="2" />-->
        </attr>
        <attr name="rippleColor" format="color|reference"/>
        <attr name="solidColor" format="color|reference"/>
        <attr name="strokeColor" format="color|reference"/>
        <attr name="strokeWidth" format="dimension|reference"/>
        <attr name="radius" format="dimension|reference"/>
    </declare-styleable>

至此,思路和方案都比较清楚了,根据个人喜好再封装一下就很顺手了

参考链接:
从代码创建 Shape Drawable
Android:RippleDrawable 水波纹/涟漪效果

上一篇下一篇

猜你喜欢

热点阅读