寫一個擴展性強的自定義控件

在一些需求中,多個界面里相似的布局钾恢,這種場景下就可以運用自定義控件減少工作量。
采用<include/>鸳址、<merge/>標(biāo)簽也是一種復(fù)用的手段瘩蚪。但是,在降低冗余代碼并避免過度繪制方面稿黍,還是自定義控件適用性更好疹瘦。

舉個例子,標(biāo)題欄是最常見的布局巡球,如:


其他也都差不多言沐,或者右邊是圖標(biāo)或者沒有東西。

懶人守則 1:能復(fù)用的酣栈,一定寫成擴展性強的組件险胰。并且要達到“懶”的目的。

首先寫布局xml界面矿筝,并以<merge/>標(biāo)簽作為根節(jié)點

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/common_header_id"
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:background="@color/colorLightGray"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/left_img"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:paddingBottom="12dp"
        android:paddingTop="12dp"
        android:scaleType="fitCenter"
        android:src="@mipmap/icon_user_friend"/>

    <TextView
        android:id="@+id/left_title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:text="@string/middle_title"
        android:textColor="@color/colorTextColor"
        android:textSize="@dimen/dp_13"/>

    <View
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <TextView
        android:id="@+id/right_title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical|right"
        android:singleLine="true"
        android:text="@string/right_title"
        android:textColor="@color/colorTextColor"
        android:textSize="@dimen/dp_13"/>
    <ImageView
        android:id="@+id/right_img"
        android:layout_width="20dp"
        android:layout_height="wrap_content"
        android:paddingBottom="15dp"
        android:paddingTop="15dp"
        android:scaleType="fitCenter"
        android:src="@mipmap/icon_arrow_right"/>
</merge>

注意:這里為了被復(fù)用的根布局便于查看起便,在不影響擴展布局賦值的情況下,根布局要達到只需要替換成merge就可以的程度。

所以榆综,將</merge>標(biāo)簽換成之前的LinearLayout之后的界面效果是:

這里還不能做到懶人守則1的標(biāo)準(zhǔn)妙痹,因為很多界面或者不需要左邊的圖標(biāo),或者不需要右邊的文字等等鼻疮。所以细诸,我們要做下面的事情~

寫一個自定義控件,采用繼承viewgroup并組合控件的方式


/**
 * Created by jinzifu on 2017/6/12.
 */

public class CommonView extends LinearLayout {
    @BindView(R.id.left_img)
    ImageView leftImg;
    @BindView(R.id.left_title)
    TextView leftTitle;
    @BindView(R.id.right_title)
    TextView rightTitle;
    @BindView(R.id.right_img)
    ImageView rightImg;

    public CommonView(Context context) {
        super(context);
    }

    public CommonView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.common_head_layout, this, true);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CommonHeaderBar);
        if (typedArray != null) {
            //獲取自定義屬性值并賦值
            setBackgroundColor(typedArray.getColor(R.styleable
                    .CommonHeaderBar_layout_background, getResources().getColor(R.color
                    .colorWhite)));
            leftImg.setImageResource(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_left_img_drawable, R.mipmap.icon_user_friend));

            leftTitle.setText(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_left_title_text, R.string.middle_title));
            leftTitle.setTextColor(typedArray.getColor(R.styleable
                    .CommonHeaderBar_left_title_color, getResources().getColor(R
                    .color.colorTextColor)));

            typedArray.recycle();
        }
    }
}

這里繼承LinearLayout布局陋守,并在其構(gòu)造方法中通過LayoutInflater.from(context).inflate的方式引入剛才的復(fù)用根布局震贵,這是典型的組合布局實現(xiàn)方式。小不同的是水评,根布局的根標(biāo)簽是<merge/>猩系,也就是說減少了不必要的層次嵌套,直接套用類LinearLayout的布局作為容器中燥。

通過context.obtainStyledAttributes方法獲取自定義的屬性值并根據(jù)擴展需要進行賦值寇甸。

配置自定義屬性,一切為了擴展和復(fù)用

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--標(biāo)題欄通用屬性-->

    <!--right_img_visible 為integer類型時默認(rèn)為0疗涉,0表示visible来候,1表示invisible,2表示gone-->
    <!--right_img_visible 為boolean類型時默認(rèn)為true糖权,true表示visible企量,false表示invisible。-->

    <attr name="layout_background" format="color|integer"/>
    <attr name="right_text_visible" format="boolean|integer"/>
    <attr name="right_img_visible" format="boolean|integer"/>
    <attr name="left_text_visible" format="boolean|integer"/>
    <attr name="left_img_visible" format="boolean|integer"/>
    <attr name="left_title_text" format="string"/>
    <attr name="right_title_text" format="string"/>
    <attr name="middle_title_text" format="string"/>
    <attr name="middle_title_color" format="color"/>
    <attr name="left_title_color" format="color"/>
    <attr name="right_title_color" format="color"/>
    <attr name="left_img_drawable" format="reference|integer"/>
    <attr name="right_img_drawable" format="reference|integer"/>

    <declare-styleable name="CommonTitleBar">
        <attr name="layout_background"/>
        <attr name="middle_title_text"/>
        <attr name="middle_title_color"/>
        <attr name="right_text_visible"/>
        <attr name="right_title_text"/>
        <attr name="right_title_color"/>
        <attr name="left_img_drawable"/>
    </declare-styleable>

    <declare-styleable name="CommonHeaderBar">
        <attr name="layout_background"/>
        <attr name="left_img_visible"/>
        <attr name="left_text_visible"/>
        <attr name="right_text_visible"/>
        <attr name="right_img_visible"/>
        <attr name="left_title_text"/>
        <attr name="right_title_text"/>
        <attr name="left_title_color"/>
        <attr name="right_title_color"/>
        <attr name="left_img_drawable"/>
        <attr name="right_img_drawable"/>
    </declare-styleable>
</resources>

目錄是在values下的attrs.xml(沒有的話自己可以新建一個)闹伪。
這里是對不同自定義控件的屬性統(tǒng)一配置沪铭,相同的屬性都放在公用區(qū),根據(jù)需要自己引用部分屬性偏瓤。

剛才我們繼承LinearLayout實現(xiàn)自定義控件杀怠,若配置一個基類LinearLayout被同類型的自定義控件繼承,更方便做一些統(tǒng)一的事情厅克。

實現(xiàn)自定義控件繼承基類赔退,做一些統(tǒng)一的、基礎(chǔ)性的事情

/**
 * Created by jinzifu on 2017/6/5.
 */

public abstract class BaseLinearLayout extends LinearLayout {
    private Context context;
    protected OnChildViewClickListener onChildViewClickListener;

    public BaseLinearLayout(Context context) {
        super(context);
        init(context,null);
    }

    public BaseLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    protected void init(Context context, AttributeSet attrs) {
        if (layoutId() != 0) {
            this.context = context;
            LayoutInflater.from(context).inflate(layoutId(), this, true);
            ButterKnife.bind(this);
            initView();
            initListener();
            initData();
        }
    }

    public void setOnChildViewClickListener(OnChildViewClickListener onChildViewClickListener) {
        this.onChildViewClickListener = onChildViewClickListener;
    }

    /**
     * 實現(xiàn)此方法在業(yè)務(wù)類中處理點擊事件
     * @param childView
     * @param action
     * @param obj
     */
    protected void onChildViewClick(View childView, String action, Object obj) {
        if (onChildViewClickListener != null) {
            onChildViewClickListener.onChildViewClick(childView, action, obj);
        }
    }

    protected abstract int layoutId();

    protected void initView() {
    }

    protected void initData() {
    }

    protected void initListener() {
    }

    protected void toast(String string) {
        Toast.makeText(context, string,Toast.LENGTH_SHORT).show();
    }
}

這里很多地方見名知義证舟, 不必詳談硕旗。單說說onChildViewClickListener這個接口吧。

因為有些場景中褪储,點擊事情或者交互操作卵渴,需要相應(yīng)的業(yè)務(wù)邏輯處理,在view層做業(yè)務(wù)邏輯處理鲤竹,不合規(guī)矩浪读,所以要引入事件的“冒泡機制”昔榴。

自定義控件里對事件的操作如下:

  @OnClick({R.id.left_img, R.id.right_title})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.left_img:
                onChildViewClick(view, "left_img", null);
                break;
            case R.id.right_title:
                toast(getResources().getString(R.string.right_title));
                break;
        }
    }
}

業(yè)務(wù)層對該事件的捕獲如下:

  titleLayout.setOnChildViewClickListener(new OnChildViewClickListener() {
            @Override
            public void onChildViewClick(View childView, String action, Object obj) {
                switch (action) {
                    case "left_img":
                        getActivity().finish();
                        break;
                    default:
                        break;
                }
            }
        });

補充下接口定義:

/**
 * Created by jinzifu on 2017/6/10.
 */
public interface OnChildViewClickListener {

    /**
     * view 內(nèi)子控件點擊事件監(jiān)聽回調(diào)
     *
     * @param childView 子控件
     * @param action    活動類型——一般寫為view的ID
     * @param obj       額外數(shù)據(jù)
     */
    void onChildViewClick(View childView, String action, Object obj);
}

繼承自定義控件基類,并根據(jù)需求配置自定義屬性

/**
 * Created by jinzifu on 2017/6/12.
 */

public class CommonHeaderBar extends BaseLinearLayout {

    @BindView(R.id.left_img)
    ImageView leftImg;
    @BindView(R.id.left_title)
    TextView leftTitle;
    @BindView(R.id.right_title)
    TextView rightTitle;
    @BindView(R.id.right_img)
    ImageView rightImg;

    public CommonHeaderBar(Context context) {
        super(context);
    }

    public CommonHeaderBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CommonHeaderBar);
        if (null != typedArray) {
            setBackgroundColor(typedArray.getColor(R.styleable
                    .CommonHeaderBar_layout_background, getResources().getColor(R.color
                    .colorWhite)));

            int left_img_visible = typedArray.getInteger(R.styleable
                    .CommonHeaderBar_left_img_visible, 0);
            int left_text_visible = typedArray.getInteger(R.styleable
                    .CommonHeaderBar_left_text_visible, 0);
            int right_text_visible = typedArray.getInteger(R.styleable
                    .CommonHeaderBar_right_text_visible, 0);
            int right_img_visible = typedArray.getInteger(R.styleable
                    .CommonHeaderBar_right_img_visible, 0);
            setViewStatus(left_img_visible, leftImg);
            setViewStatus(left_text_visible, leftTitle);
            setViewStatus(right_text_visible, rightTitle);
            setViewStatus(right_img_visible, rightImg);

            leftImg.setImageResource(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_left_img_drawable, R.mipmap.icon_user_friend));
            rightImg.setImageResource(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_right_img_drawable, R.mipmap.icon_arrow_right));

            leftTitle.setTextColor(typedArray.getColor(R.styleable
                    .CommonHeaderBar_left_title_color, getResources().getColor(R
                    .color.colorTextColor)));
            leftTitle.setText(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_left_title_text, R.string.middle_title));

            rightTitle.setTextColor(typedArray.getColor(R.styleable
                    .CommonHeaderBar_right_title_color, getResources().getColor(R
                    .color.colorTextColor)));
            rightTitle.setText(typedArray.getResourceId(R.styleable
                    .CommonHeaderBar_right_title_text, R.string.right_title));

            typedArray.recycle();
        }
    }

    private void setViewStatus(int flag, View view) {
        switch (flag) {
            case 0:
                view.setVisibility(VISIBLE);
                break;
            case 1:
                view.setVisibility(INVISIBLE);
                break;
            case 2:
                view.setVisibility(GONE);
                break;
            default:
                view.setVisibility(VISIBLE);
                break;
        }
    }

    @Override
    protected int layoutId() {
        return R.layout.common_head_layout;
    }

}

CustomizeViewFragment里的代碼如下:

public class CustomizeViewFragment extends BaseFragment {
    Unbinder unbinder;
    @BindView(R.id.title_layout)
    CommonTitleBar titleLayout;
    @BindView(R.id.header_layout)
    CommonHeaderBar headerLayout;
    @BindView(R.id.header_layout2)
    CommonHeaderBar headerLayout2;
    @BindView(R.id.header_layout3)
    CommonHeaderBar headerLayout3;
    @BindView(R.id.header_layout4)
    CommonHeaderBar headerLayout4;

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_common_view;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
            savedInstanceState) {
        View rootView = super.onCreateView(inflater, container, savedInstanceState);
        unbinder = ButterKnife.bind(this, rootView);
        return rootView;
    }

    @Override
    protected void init() {
        titleLayout.setOnChildViewClickListener(new OnChildViewClickListener() {
            @Override
            public void onChildViewClick(View childView, String action, Object obj) {
                switch (action) {
                    case "left_img":
                        getActivity().finish();
                        break;
                    default:
                        break;
                }
            }
        });

        headerLayout.findViewById(R.id.right_title).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toast("right_title");
            }
        });
        headerLayout2.findViewById(R.id.left_title).setOnClickListener(new View
                .OnClickListener() {
            @Override
            public void onClick(View v) {
                toast("left_title");
            }
        });
        headerLayout4.findViewById(R.id.left_img).setOnClickListener(new View
                .OnClickListener() {
            @Override
            public void onClick(View v) {
                toast("left_img");
            }
        });
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }

    @OnClick({R.id.header_layout, R.id.header_layout2, R.id.header_layout3, R.id.header_layout4})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.header_layout:
                toast("header_layout");
                break;
            case R.id.header_layout2:
                toast("header_layout2");
                break;
            case R.id.header_layout3:
                toast("header_layout3");
                break;
            case R.id.header_layout4:
                toast("header_layout4");
                break;
        }
    }
}

CustomizeViewFragment的布局如下:

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

    <com.jzf.myactivity.view.CommonTitleBar
        android:id="@+id/title_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_title_height"
        king:layout_background="@color/colorPrimary"
        king:middle_title_color="@color/colorWhite"
        king:middle_title_text="@string/middle_title"
        king:right_text_visible="true"
        king:right_title_color="@color/colorWhite"
        king:right_title_text="@string/right_title">
    </com.jzf.myactivity.view.CommonTitleBar>

    <com.jzf.myactivity.view.CommonHeaderBar
        android:id="@+id/header_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_header_height"
        android:layout_marginBottom="1dp"
        king:layout_background="@color/colorLightGray">
    </com.jzf.myactivity.view.CommonHeaderBar>

    <com.jzf.myactivity.view.CommonHeaderBar
        android:id="@+id/header_layout2"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_header_height"
        android:layout_marginBottom="1dp"
        android:paddingLeft="10dp"
        king:layout_background="@color/colorLightGray"
        king:left_img_visible="2"
        king:right_text_visible="2">
    </com.jzf.myactivity.view.CommonHeaderBar>

    <com.jzf.myactivity.view.CommonHeaderBar
        android:id="@+id/header_layout3"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_header_height"
        android:layout_marginBottom="1dp"
        king:layout_background="@color/colorLightGray"
        king:left_text_visible="1">
    </com.jzf.myactivity.view.CommonHeaderBar>

    <com.jzf.myactivity.view.CommonHeaderBar
        android:id="@+id/header_layout4"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_header_height"
        king:layout_background="@color/colorLightGray"
        king:left_text_visible="1"
        king:right_text_visible="2">
    </com.jzf.myactivity.view.CommonHeaderBar>
</LinearLayout>

最后貼圖

https://github.com/jinzifu/myactivity.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碘橘,一起剝皮案震驚了整個濱河市互订,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌痘拆,老刑警劉巖仰禽,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纺蛆,居然都是意外死亡吐葵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門桥氏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來温峭,“玉大人,你說我怎么就攤上這事字支》锊兀” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵堕伪,是天一觀的道長揖庄。 經(jīng)常有香客問我,道長欠雌,這世上最難降的妖魔是什么蹄梢? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮桨昙,結(jié)果婚禮上检号,老公的妹妹穿的比我還像新娘。我一直安慰自己蛙酪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布翘盖。 她就那樣靜靜地躺著桂塞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪馍驯。 梳的紋絲不亂的頭發(fā)上阁危,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音汰瘫,去河邊找鬼狂打。 笑死,一個胖子當(dāng)著我的面吹牛混弥,可吹牛的內(nèi)容都是我干的趴乡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晾捏!你這毒婦竟也來了蒿涎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤惦辛,失蹤者是張志新(化名)和其女友劉穎劳秋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胖齐,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡玻淑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了呀伙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片补履。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖区匠,靈堂內(nèi)的尸體忽然破棺而出干像,到底是詐尸還是另有隱情,我是刑警寧澤驰弄,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布麻汰,位于F島的核電站,受9級特大地震影響戚篙,放射性物質(zhì)發(fā)生泄漏五鲫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一岔擂、第九天 我趴在偏房一處隱蔽的房頂上張望位喂。 院中可真熱鬧,春花似錦乱灵、人聲如沸塑崖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规婆。三九已至,卻和暖如春蝉稳,著一層夾襖步出監(jiān)牢的瞬間抒蚜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工耘戚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗡髓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓收津,卻偏偏與公主長得像饿这,于是被迫代替她去往敵國和親浊伙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容