自定義View——ShapeTextView(可設置背景邊框,Selector選擇器)

簡介

日常項目開發中,經常會遇到把TextView設置成一個按鈕,就像下面這個:


按鈕.png

而一般的實現方式就是在res/drawable目錄下定義一個drawable.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="10dp" />
    <solid android:color="@color/blue" />
</shape>

然后設置為TextView的背景,如果需要選中時改變狀態,可以這么設置:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/bule" android:state_pressed="false" />
    <item android:drawable="@drawable/red" android:state_pressed="true" />
    <item android:drawable="@drawable/bule" />
</selector>

這樣寫的好處是簡單易懂,但是如果項目中用到了很多不同風格的邊框背景時,就會創建非常多的相似的資源文件,比較麻煩。為了避免這種麻煩,這里使用Java代碼來創建Drawable。


狀態說明.png

自定義ShapeTextView

使用自定義view繼承TextView來實現上面說的效果

定義屬性

在res/values/attrs.xml中添加自定義屬性

<!--自定義矩形邊框的TextView-->
    <declare-styleable name="ShapeTextView">
        <!--是否使用Selector選擇器-->
        <attr name="openSelector" format="boolean" />
        <!--填充色-->
        <attr name="solidColor" format="color" />
        <!--邊框色-->
        <attr name="strokeColor" format="color" />
        <!--按下填充色-->
        <attr name="solidTouchColor" format="color" />
        <!--按下邊框色-->
        <attr name="strokeTouchColor" format="color" />
        <!--邊框寬度-->
        <attr name="strokeWidth" format="dimension" />
        <!--圓角弧度-->
        <attr name="radius" format="dimension" />
        <!--四個角的圓角弧度-->
        <attr name="topLeftRadius" format="dimension" />
        <attr name="topRightRadius" format="dimension" />
        <attr name="bottomLeftRadius" format="dimension" />
        <attr name="bottomRightRadius" format="dimension" />
        <!--虛線邊框寬度-->
        <attr name="dashWidth" format="dimension" />
        <!--虛線邊框間隙-->
        <attr name="dashGap" format="dimension" />
    </declare-styleable>

自定義View繼承TextView

/**
 * @author Lin
 * @date 2019/8/22
 * @description 實現自定義圓角背景
 * 支持
 * 1.四邊圓角
 * 2.指定邊圓角
 * 3.支持填充色以及邊框色,邊框虛線
 * 4.支持按下效果
 */
@SuppressLint("AppCompatCustomView")
public class ShapeTextView extends TextView {
    private boolean openSelector;
    //自定背景邊框Drawable
    private GradientDrawable gradientDrawable;
    //按下時的Drawable
    private GradientDrawable selectorDrawable;
    //填充色
    private int solidColor = 0;
    //邊框色
    private int strokeColor = 0;
    //按下填充色
    private int solidTouchColor = 0;
    //按下邊框色
    private int strokeTouchColor = 0;
    //按下字體色
    private int textTouchColor = 0;
    //邊框寬度
    private int strokeWidth = 0;
    //四個角的弧度
    private float radius;
    private float topLeftRadius;
    private float topRightRadius;
    private float bottomLeftRadius;
    private float bottomRightRadius;
    //邊框虛線的寬度
    float dashWidth = 0;
    //邊框虛線的間隙
    float dashGap = 0;
    //字體色
    private int textColor = 0;


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

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

    public ShapeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
        //默認背景
        gradientDrawable = getNeedDrawable(new float[]{topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
                        bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius},
                solidColor, strokeWidth, strokeColor, dashWidth, dashGap);
        //如果設置了選中時的背景
        if (openSelector) {
            selectorDrawable = getNeedDrawable(new float[]{topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
                            bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius},
                    solidTouchColor, strokeWidth, strokeTouchColor, dashWidth, dashGap);

            //動態生成Selector
            StateListDrawable stateListDrawable = new StateListDrawable();
            //是否按下
            int pressed = android.R.attr.state_pressed;

            stateListDrawable.addState(new int[]{pressed}, selectorDrawable);
            stateListDrawable.addState(new int[]{}, gradientDrawable);

            setBackground(stateListDrawable);
        } else {
            setBackground(gradientDrawable);
        }
    }

    /**
     * 初始化參數
     *
     * @param context
     * @param attrs
     */
    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeTextView, 0, 0);

        openSelector = ta.getBoolean(R.styleable.ShapeTextView_openSelector, false);

        solidColor = ta.getInteger(R.styleable.ShapeTextView_solidColor, 0x00000000);
        strokeColor = ta.getInteger(R.styleable.ShapeTextView_strokeColor, 0x00000000);

        solidTouchColor = ta.getInteger(R.styleable.ShapeTextView_solidTouchColor, 0x00000000);
        strokeTouchColor = ta.getInteger(R.styleable.ShapeTextView_strokeTouchColor, 0x00000000);
        textTouchColor = ta.getInteger(R.styleable.ShapeTextView_textTouchColor, 0x00000000);
        textColor = getCurrentTextColor();
        strokeWidth = (int) ta.getDimension(R.styleable.ShapeTextView_strokeWidth, 0);

        //四個角單獨設置會覆蓋radius設置
        radius = ta.getDimension(R.styleable.ShapeTextView_radius, 0);
        topLeftRadius = ta.getDimension(R.styleable.ShapeTextView_topLeftRadius, radius);
        topRightRadius = ta.getDimension(R.styleable.ShapeTextView_topRightRadius, radius);
        bottomLeftRadius = ta.getDimension(R.styleable.ShapeTextView_bottomLeftRadius, radius);
        bottomRightRadius = ta.getDimension(R.styleable.ShapeTextView_bottomRightRadius, radius);

        dashGap = ta.getDimension(R.styleable.ShapeTextView_dashGap, 0);
        dashWidth = ta.getDimension(R.styleable.ShapeTextView_dashWidth, 0);

        ta.recycle();
    }

    /**
     * @param radius      四個角的半徑
     * @param colors      漸變的顏色
     * @param strokeWidth 邊框寬度
     * @param strokeColor 邊框顏色
     * @return
     */
    public static GradientDrawable getNeedDrawable(float[] radius, int[] colors, int strokeWidth, int strokeColor) {
        //TODO:判斷版本是否大于16  項目中默認的都是Linear散射 都是從左到右 都是只有開始顏色和結束顏色
        GradientDrawable drawable;
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            drawable = new GradientDrawable();
            drawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
            drawable.setColors(colors);
        } else {
            drawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors);
        }

        drawable.setCornerRadii(radius);
        drawable.setStroke(strokeWidth, strokeColor);
        drawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
        return drawable;
    }

    /**
     * @param radius      四個角的半徑
     * @param bgColor     背景顏色
     * @param strokeWidth 邊框寬度
     * @param strokeColor 邊框顏色
     * @return
     */
    public static GradientDrawable getNeedDrawable(float[] radius, int bgColor, int strokeWidth, int strokeColor) {
        GradientDrawable drawable = new GradientDrawable();
        drawable.setShape(GradientDrawable.RECTANGLE);
        drawable.setCornerRadii(radius);
        drawable.setStroke(strokeWidth, strokeColor);
        drawable.setColor(bgColor);
        return drawable;
    }

    /**
     * @param radius      四個角的半徑
     * @param bgColor     背景顏色
     * @param strokeWidth 邊框寬度
     * @param strokeColor 邊框顏色
     * @param dashWidth   虛線邊框寬度
     * @param dashGap     虛線邊框間隙
     * @return
     */
    public static GradientDrawable getNeedDrawable(float[] radius, int bgColor, int strokeWidth, int strokeColor, float dashWidth, float dashGap) {
        GradientDrawable drawable = new GradientDrawable();
        drawable.setShape(GradientDrawable.RECTANGLE);
        drawable.setCornerRadii(radius);
        drawable.setStroke(strokeWidth, strokeColor, dashWidth, dashGap);
        drawable.setColor(bgColor);
        return drawable;
    }
}

使用示例

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingTop="50dp">

    <com.lin.module_base.view.ShapeTextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_margin="5dp"
        android:gravity="center"
        android:text="@string/about"
        app:openSelector="true"
        app:radius="10dp"
        app:solidColor="@color/white"
        app:solidTouchColor="@color/gray_light"
        app:strokeColor="@color/blue"
        app:strokeTouchColor="@color/red"
        app:strokeWidth="1dp"
        app:textTouchColor="@color/red" />

    <com.lin.module_base.view.ShapeTextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_margin="5dp"
        android:gravity="center"
        android:text="@string/about"
        app:bottomRightRadius="20dp"
        app:openSelector="true"
        app:solidColor="@color/white"
        app:solidTouchColor="@color/gray_light"
        app:strokeColor="@color/blue"
        app:strokeTouchColor="@color/red"
        app:strokeWidth="1dp"
        app:textTouchColor="@color/red"
        app:topLeftRadius="20dp" />

    <com.lin.module_base.view.ShapeTextView
        android:id="@+id/text2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_margin="5dp"
        android:gravity="center"
        android:text="@string/about"
        app:dashGap="1dp"
        app:dashWidth="2dp"
        app:openSelector="true"
        app:radius="10dp"
        app:solidColor="@color/white"
        app:strokeColor="@color/blue"
        app:strokeTouchColor="@color/red"
        app:strokeWidth="0.5dp"
        app:textTouchColor="@color/red" />

    <com.lin.module_base.view.ShapeTextView
        android:id="@+id/text3"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="center_horizontal"
        android:layout_margin="5dp"
        android:gravity="center"
        android:text="@string/about"
        app:openSelector="true"
        app:radius="25dp"
        app:solidColor="@color/blue"
        app:strokeColor="@color/blue"
        app:strokeTouchColor="@color/red"
        app:strokeWidth="1dp"
        app:textTouchColor="@color/red" />

</LinearLayout>

效果如下圖:


image.png

踩坑

如果需要selector選擇器的效果,需要在xml中設置app:openSelector="true"
然后要給View設置監聽事件

 text1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

總結

這里我們使用了Java代碼來創建Drawable,可以把常用方法提取到工具類中。
日常開發中也有其他類似的需要定義CheckBox、RadioButton等背景的,也可以參考這種做法。

參考:
Drawable子類之——StateListDrawable (選擇器)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容

  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,478評論 0 17
  • 前言 如上圖所示,相信可愛的安卓程序猿們在開發中經常會遇到這種樣式的UI開發。其實上面這種布局很簡單,沒有難度,只...
    笑哥哥閱讀 3,871評論 0 4
  • 【Android 動畫】 動畫分類補間動畫(Tween動畫)幀動畫(Frame 動畫)屬性動畫(Property ...
    Rtia閱讀 6,198評論 1 38
  • 很早看過這篇文章,并做了筆記,后來看到群里的小伙伴有問相關Drawable的問題,就把這篇翻譯過來的文章給放出來了...
    Kotyo閱讀 1,480評論 0 5
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,730評論 0 10