歡迎光臨
每天分享高質量文章

Android通用UI封裝—-“我的”頁面Item

作者:chaohx

連結:https://www.jianshu.com/p/9fa393aa1406

問題

程式員最討厭的事情是什麼? 不是程式碼多到寫不完,而是重覆的程式碼要寫無數遍。身為程式員的我們,肯定經常被這個問題所困擾。就比如Android APP中,“我的”頁面中的每一個Item,就像下圖這樣的這樣的,

“我的”頁面常見佈局

還有像這種資料錄入頁面的:

資料錄入頁面

像這種頁面佈局是極其常見的,幾乎在每個APP中都有那麼一兩個這樣的頁面。這種頁面讓Android開發人員很是頭疼,開發難度倒是不難,就是重覆程式碼太多,沒有挑戰性。用一句不好聽的話來說就是“程式碼又臭又長”。所以呢,我就對此做了一個封裝,達到一行程式碼就是一行item。

使用效果

先來看一下使用效果,看看封裝後,使用起來有多簡單。

程式碼

對應的效果

上面的程式碼對有一定Android開發經驗的你應該不難理解,起始就是new一個Item物件,設定其屬性(具體下麵解釋),設定監聽事件(具體下麵解釋),新增到LinnearLayout容器中。

封裝步驟

1、佈局

佈局不難,也就是把通常寫的item佈局抽取出來。主要包括這麼幾個部分:上分割線、下分割線、左Icon、中間文字(偏左那個)、中間輸入框、右邊文字、右邊Icon(預設右箭頭)。

直接上程式碼,根據程式碼裡的註釋,很好理解。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    
    <View
        android:id="@+id/divider_top"
        android:layout_width="fill_parent"
        android:layout_height="1px"
        android:background="#efefef" />


    
    <LinearLayout
        android:id="@+id/ll_root"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingBottom="10dp"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:paddingTop="10dp">


        
        <ImageView
            android:id="@+id/iv_left_icon"
            android:layout_width="20dp"
            android:layout_height="20dp" />


        
        <TextView
            android:id="@+id/tv_text_content"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="12dp"
            android:layout_weight="1"
            android:textSize="14sp" />


        
        <EditText
            android:id="@+id/edit_content"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:background="@color/transparent"
            android:lines="1"
            android:maxLines="1"
            android:textSize="14sp" />


        
        <TextView
            android:id="@+id/tv_right_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp" />


        
        <ImageView
            android:id="@+id/iv_right_icon"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingLeft="10dp"
            android:src="@mipmap/homepage_right_arrow" />

    LinearLayout>


    
    <View
        android:id="@+id/divider_bottom"
        android:layout_width="fill_parent"
        android:layout_height="1px"
        android:background="#efefef" />


LinearLayout>

2、MyOneLineView.class的封裝

  • 佈局的引入
    Xml佈局寫好之後我們還需要一個Java類,取名為MyOneLineView.class
    該類繼承LinearLayout或RelativeLayout,使其成為一個容器。
    然後,透過LayoutInflater將我們前面寫好的佈局進入MyOneLineView容器中。

public class MyOneLineView extends LinearLayout {
    //各各控制元件
    private View dividerTop, dividerBottom;
    private LinearLayout llRoot;
    private ImageView ivLeftIcon;
    private TextView tvTextContent;
    private EditText editContent;
    private TextView tvRightText;
    private ImageView ivRightIcon;

    public MyOneLineView(Context context) {  //該建構式使其,可以在Java程式碼中建立
        super(context);
    }

    public MyOneLineView(Context context, AttributeSet attrs) {//該建構式使其可以在XML佈局中使用
        super(context, attrs);
    }

    /**
     * 初始化各個控制元件
     */

    public MyOneLineView init() {
    //引入之前的xml佈局
    LayoutInflater.from(getContext()).inflate(R.layout.layout_my_one_line, thistrue);
        llRoot = (LinearLayout) findViewById(R.id.ll_root);
        dividerTop = findViewById(R.id.divider_top);
        dividerBottom = findViewById(R.id.divider_bottom);
        ivLeftIcon = (ImageView) findViewById(R.id.iv_left_icon);
        tvTextContent = (TextView) findViewById(R.id.tv_text_content);
        editContent = (EditText) findViewById(R.id.edit_content);
        tvRightText = (TextView) findViewById(R.id.tv_right_text);
        ivRightIcon = (ImageView) findViewById(R.id.iv_right_icon);
        return this;
    }
}

這樣 MyOneLineView.class 每 new 一下就代表這一個 Item。只不過他的樣式還只是預設狀態。下麵我們就對它的樣式操作進行封裝。

  • 分割線封裝
    前面佈局中也介紹了,每個Item都有上下兩條分割線,預設上分割線隱藏,下分割線顯示,寬度充滿全屏,高度1dp,顏色是灰色。
    我們封裝幾個方法用於對分割線樣式的改變:

 /**
     * 設定上下分割線的顯示情況
     *
     * @return
     */

    public MyOneLineView showDivider(Boolean showDividerTop, Boolean showDivderBottom) {
        if (showDividerTop) {
            dividerTop.setVisibility(VISIBLE);
        } else {
            dividerTop.setVisibility(GONE);
        }
        if (showDivderBottom) {
            dividerBottom.setVisibility(VISIBLE);
        } else {
            dividerBottom.setVisibility(GONE);
        }
        return this;
    }

    /**
     * 設定上分割線的顏色
     *
     * @return
     */

    public MyOneLineView setDividerTopColor(int dividerTopColorRes) {
        dividerTop.setBackgroundColor(getResources().getColor(dividerTopColorRes));
        return this;
    }

    /**
     * 設定上分割線的高度
     *
     * @return
     */

    public MyOneLineView setDividerTopHigiht(int dividerTopHigihtDp) {
        ViewGroup.LayoutParams layoutParams = dividerTop.getLayoutParams();
        layoutParams.height = DensityUtils.dp2px(getContext(), dividerTopHigihtDp);
        dividerTop.setLayoutParams(layoutParams);
        return this;
    }


  • 每個child的封裝
    同上面對分割線樣式的封裝,我們就可以對每個child的樣式提供方法,需要怎樣改變child的樣式就封裝對應的方法即可。直接看程式碼:

    /**
     * 設定root的paddingTop 與 PaddingBottom 從而控制整體的行高
     * paddingLeft 與 paddingRight 保持預設 20dp
     */

    public MyOneLineView setRootPaddingTopBottom(int paddintTop, int paddintBottom) {
        llRoot.setPadding(DensityUtils.dp2px(getContext(), 20),
                DensityUtils.dp2px(getContext(), paddintTop),
                DensityUtils.dp2px(getContext(), 20),
                DensityUtils.dp2px(getContext(), paddintBottom));
        return this;
    }

    /**
     * 設定root的paddingLeft 與 PaddingRight 從而控制整體的行高
     * 


     * paddingTop 與 paddingBottom 保持預設 15dp
     */


    public MyOneLineView setRootPaddingLeftRight(int paddintTop, int paddintRight) {
        llRoot.setPadding(DensityUtils.dp2px(getContext(), paddintTop),
                DensityUtils.dp2px(getContext(), 15),
                DensityUtils.dp2px(getContext(), paddintRight),
                DensityUtils.dp2px(getContext(), 15));
        return this;
    }

    /**
     * 設定左邊Icon
     *
     * @param iconRes
     */

    public MyOneLineView setLeftIcon(int iconRes) {
        ivLeftIcon.setImageResource(iconRes);
        return this;
    }

    /**
     * 設定左邊Icon顯示與否
     *
     * @param showLeftIcon
     */

    public MyOneLineView showLeftIcon(boolean showLeftIcon) {
        if (showLeftIcon) {
            ivLeftIcon.setVisibility(VISIBLE);
        } else {
            ivLeftIcon.setVisibility(GONE);
        }
        return this;
    }

    /**
     * 設定右邊Icon 以及Icon的寬高
     */

    public MyOneLineView setLeftIconSize(int widthDp, int heightDp) {
        ViewGroup.LayoutParams layoutParams = ivLeftIcon.getLayoutParams();
        layoutParams.width = DensityUtils.dp2px(getContext(), widthDp);
        layoutParams.height = DensityUtils.dp2px(getContext(), heightDp);
        ivLeftIcon.setLayoutParams(layoutParams);
        return this;
    }

    /**
     * 設定中間的文字內容
     *
     * @param textContent
     * @return
     */

    public MyOneLineView setTextContent(String textContent) {
        tvTextContent.setText(textContent);
        return this;
    }

    /**
     * 設定中間的文字顏色
     *
     * @return
     */

    public MyOneLineView setTextContentColor(int colorRes) {
        tvTextContent.setTextColor(getResources().getColor(colorRes));
        return this;
    }

    /**
     * 設定中間的文字大小
     *
     * @return
     */

    public MyOneLineView setTextContentSize(int textSizeSp) {
        tvTextContent.setTextSize(textSizeSp);
        return this;
    }

這一部分程式碼都比較類似,所以就貼出部分程式碼來。

細心的同學會發現,大部分的方法都是傳回了this.這樣做的原因就是想實現鏈式結構,鏈式結構也是最近比較流行的。

程式碼中有這樣的程式碼:
DensityUtils.dp2px(getContext(), widthDp);
DensityUtils 也是一個封裝的工具類,用於dp sp 與px 的轉換,用興趣的同學可以看這篇文章Android單位轉換—-常用單位轉換工具類

  • 點選事件的封裝
    樣式有了,接下來就應該考慮點選事件了,這一類item通常有兩個點選事件,一整行的點選事件、右箭頭的點選事件。因此這裡封裝了兩個ClickListener

public class MyOneLineView extends LinearLayout {
....
    /**
     * 整個一行被點選
     */

    public static interface OnRootClickListener {
        void onRootClick(View view);
    }
    /**
     * 右邊箭頭的點選事件
     */

    public static interface OnArrowClickListener {
        void onArrowClick(View view);
    }
    public MyOneLineView setOnRootClickListener(final OnRootClickListener onRootClickListener, final int tag) {
        llRoot.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                llRoot.setTag(tag);
                onRootClickListener.onRootClick(llRoot);
            }
        });
        return this;
    }
    public MyOneLineView setOnArrowClickListener(final OnArrowClickListener onArrowClickListener, final int tag) {

        ivRightIcon.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ivRightIcon.setTag(tag);
                onArrowClickListener.onArrowClick(ivRightIcon);
            }
        });
        return this;
    }
....
}

小夥伴們可能會問,這裡為什麼要傳入tag。不用急,請看下麵的使用程式碼:

public class MineFragment extends SimpleFragment implements MyOneLineView.OnRootClickListener{
    ......
    initView(){
        //新增我的下載
        llRoot.addView(new MyOneLineView(getContext())
                .initMine(R.mipmap.mine_download, "我的下載"""true)
                .setOnRootClickListener(this1));
        //新增我的收藏
        llRoot.addView(new MyOneLineView(getContext())
                .initMine(R.mipmap.mine_collection, "我的收藏"""true)
                .setOnRootClickListener(this2));
    }

    @Override
    public void onRootClick(View view) {
        switch ((int) view.getTag()) {
            case 1:
                startActivity(new Intent(getContext(), MyDownloadActivity.class));
                break;
            case 2:
                startActivity(new Intent(getContext(), MyCollectionActivity.class));
                break;
    }

    ......
}

看了這段程式碼,大家應該直到為什麼要設定tag了吧。原因就是希望共用onRootClick方法。有同學會問,為什麼不用id呢?那是因為,每一個item用的是同一個xml佈局,也就是用的同一個id,所以這裡不能像以往一樣用id。

  • 常用場景init()
    到這裡MyOnLineView已經封裝好了,但是為了方便,我們可以為使用頻率很高的一些情況提供快速的建立方法,就想“我的”頁面的item。大家可以更具自己專案不同來做調整。

 /**
     * 預設情況下的樣子  icon + 文字 + 右箭頭 + 下分割線
     *
     * @param iconRes     icon圖片
     * @param textContent 文字內容
     */

    public MyOneLineView init(int iconRes, String textContent) {
        init();
        showDivider(falsetrue);
        setLeftIcon(iconRes);
        setTextContent(textContent);
        showEdit(false);
        setRightText("");
        showArrow(true);
        return this;
    }

    /**
     * 我的頁面每一行  icon + 文字 + 右箭頭(顯示/不顯示) + 右箭頭左邊的文字(顯示/不顯示)+ 下分割線
     *
     * @param iconRes     icon圖片
     * @param textContent 文字內容
     */

    public MyOneLineView initMine(int iconRes, String textContent, String textRight, boolean showArrow) {
        init(iconRes, textContent);
        setRightText(textRight);
        showArrow(showArrow);
        return this;
    }

    /**
     * icon + 文字 + edit + 下分割線
     *
     * @return
     */

    public MyOneLineView initItemWidthEdit(int iconRes, String textContent, String editHint) {
        init(iconRes, textContent);
        showEdit(true);
        setEditHint(editHint);
        showArrow(false);
        return this;
    }

總結

文章到此結束,希望這篇文章對大家有所幫助,提高開發效率。
完整程式碼:
https://github.com/chaohengxing/MyOneLineView.git


●編號364,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

Java程式設計

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂