問題
程序員最討厭的事情是什么? 不是代碼多到寫不完丛晌,而是重復(fù)的代碼要寫無數(shù)遍仅炊。身為程序員的我們,肯定經(jīng)常被這個問題所困擾澎蛛。就比如Android APP中抚垄,“我的”頁面中的每一個Item,就像下圖這樣的這樣的谋逻,
還有像這種資料錄入頁面的:
像這種頁面布局是極其常見的呆馁,幾乎在每個APP中都有那么一兩個這樣的頁面。這種頁面讓Android開發(fā)人員很是頭疼毁兆,開發(fā)難度倒是不難浙滤,就是重復(fù)代碼太多,沒有挑戰(zhàn)性气堕。用一句不好聽的話來說就是“代碼又臭又長”纺腊。所以呢,我就對此做了一個封裝茎芭,達(dá)到一行代碼就是一行item揖膜。
使用效果
先來看一下使用效果,看看封裝后骗爆,使用起來有多簡單次氨。
上面的代碼對有一定Android開發(fā)經(jīng)驗(yàn)的你應(yīng)該不難理解,起始就是new一個Item對象摘投,設(shè)置其屬性(具體下面解釋)煮寡,設(shè)置監(jiān)聽事件(具體下面解釋),添加到LinnearLayout容器中犀呼。
封裝步驟
1.布局
布局不難幸撕,也就是把通常寫的item布局抽取出來。主要包括這么幾個部分:上分割線外臂、下分割線坐儿、左Icon、中間文字(偏左那個)、中間輸入框貌矿、右邊文字炭菌、右邊Icon(默認(rèn)右箭頭)。
直接上代碼逛漫,根據(jù)代碼里的注釋黑低,很好理解。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--item的上分割線-->
<View
android:id="@+id/divider_top"
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="#efefef" />
<!--item內(nèi)容部分的容器-->
<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">
<!--item左邊的Icon-->
<ImageView
android:id="@+id/iv_left_icon"
android:layout_width="20dp"
android:layout_height="20dp" />
<!--item中間部分的文字-->
<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" />
<!--item 中間部分的輸入框(有則顯示五則隱藏酌毡,默認(rèn)隱藏)-->
<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" />
<!--item右邊的文字-->
<TextView
android:id="@+id/tv_right_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
<!--item右邊的Icon 默認(rèn)是向右的箭頭-->
<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>
<!--item的下分割線-->
<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將我們前面寫好的布局進(jìn)入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) { //該構(gòu)造函數(shù)使其旭蠕,可以在Java代碼中創(chuàng)建
super(context);
}
public MyOneLineView(Context context, AttributeSet attrs) {//該構(gòu)造函數(shù)使其可以在XML布局中使用
super(context, attrs);
}
/**
* 初始化各個控件
*/
public MyOneLineView init() {
//引入之前的xml布局
LayoutInflater.from(getContext()).inflate(R.layout.layout_my_one_line, this, true);
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停团。只不過他的樣式還只是默認(rèn)狀態(tài)。下面我們就對它的樣式操作進(jìn)行封裝掏熬。
-
分割線封裝
前面布局中也介紹了客蹋,每個Item都有上下兩條分割線,默認(rèn)上分割線隱藏孽江,下分割線顯示讶坯,寬度充滿全屏,高度1dp岗屏,顏色是灰色辆琅。
我們封裝幾個方法用于對分割線樣式的改變:
/**
* 設(shè)置上下分割線的顯示情況
*
* @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;
}
/**
* 設(shè)置上分割線的顏色
*
* @return
*/
public MyOneLineView setDividerTopColor(int dividerTopColorRes) {
dividerTop.setBackgroundColor(getResources().getColor(dividerTopColorRes));
return this;
}
/**
* 設(shè)置上分割線的高度
*
* @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的樣式就封裝對應(yīng)的方法即可婉烟。直接看代碼:
/**
* 設(shè)置root的paddingTop 與 PaddingBottom 從而控制整體的行高
* paddingLeft 與 paddingRight 保持默認(rèn) 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;
}
/**
* 設(shè)置root的paddingLeft 與 PaddingRight 從而控制整體的行高
* <p>
* paddingTop 與 paddingBottom 保持默認(rèn) 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;
}
/**
* 設(shè)置左邊Icon
*
* @param iconRes
*/
public MyOneLineView setLeftIcon(int iconRes) {
ivLeftIcon.setImageResource(iconRes);
return this;
}
/**
* 設(shè)置左邊Icon顯示與否
*
* @param showLeftIcon
*/
public MyOneLineView showLeftIcon(boolean showLeftIcon) {
if (showLeftIcon) {
ivLeftIcon.setVisibility(VISIBLE);
} else {
ivLeftIcon.setVisibility(GONE);
}
return this;
}
/**
* 設(shè)置右邊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;
}
/**
* 設(shè)置中間的文字內(nèi)容
*
* @param textContent
* @return
*/
public MyOneLineView setTextContent(String textContent) {
tvTextContent.setText(textContent);
return this;
}
/**
* 設(shè)置中間的文字顏色
*
* @return
*/
public MyOneLineView setTextContentColor(int colorRes) {
tvTextContent.setTextColor(getResources().getColor(colorRes));
return this;
}
/**
* 設(shè)置中間的文字大小
*
* @return
*/
public MyOneLineView setTextContentSize(int textSizeSp) {
tvTextContent.setTextSize(textSizeSp);
return this;
}
這一部分代碼都比較類似,所以就貼出部分代碼來暇屋。
細(xì)心的同學(xué)會發(fā)現(xiàn)似袁,大部分的方法都是返回了this.這樣做的原因就是想實(shí)現(xiàn)鏈?zhǔn)浇Y(jié)構(gòu),鏈?zhǔn)浇Y(jié)構(gòu)也是最近比較流行的咐刨。
代碼中有這樣的代碼:
DensityUtils.dp2px(getContext(), widthDp)昙衅;
DensityUtils也是一個封裝的工具類,用于dp sp 與px 的轉(zhuǎn)換定鸟,用興趣的同學(xué)可以看這篇文章Android單位轉(zhuǎn)換----常用單位轉(zhuǎn)換工具類
-
點(diǎn)擊事件的封裝
樣式有了而涉,接下來就應(yīng)該考慮點(diǎn)擊事件了,這一類item通常有兩個點(diǎn)擊事件联予,一整行的點(diǎn)擊事件啼县、右箭頭的點(diǎn)擊事件材原。因此這里封裝了兩個ClickListener
public class MyOneLineView extends LinearLayout {
....
/**
* 整個一行被點(diǎn)擊
*/
public static interface OnRootClickListener {
void onRootClick(View view);
}
/**
* 右邊箭頭的點(diǎn)擊事件
*/
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(this, 1));
//添加我的收藏
llRoot.addView(new MyOneLineView(getContext())
.initMine(R.mipmap.mine_collection, "我的收藏", "", true)
.setOnRootClickListener(this, 2));
}
@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;
}
......
}
看了這段代碼,大家應(yīng)該直到為什么要設(shè)置tag了吧子刮。原因就是希望共用onRootClick方法客叉。有同學(xué)會問,為什么不用id呢话告?那是因?yàn)椋恳粋€item用的是同一個xml布局卵慰,也就是用的同一個id沙郭,所以這里不能像以往一樣用id。
-
常用場景init()
到這里MyOnLineView已經(jīng)封裝好了裳朋,但是為了方便病线,我們可以為使用頻率很高的一些情況提供快速的創(chuàng)建方法,就想“我的”頁面的item鲤嫡。大家可以更具自己項(xiàng)目不同來做調(diào)整送挑。
/**
* 默認(rèn)情況下的樣子 icon + 文字 + 右箭頭 + 下分割線
*
* @param iconRes icon圖片
* @param textContent 文字內(nèi)容
*/
public MyOneLineView init(int iconRes, String textContent) {
init();
showDivider(false, true);
setLeftIcon(iconRes);
setTextContent(textContent);
showEdit(false);
setRightText("");
showArrow(true);
return this;
}
/**
* 我的頁面每一行 icon + 文字 + 右箭頭(顯示/不顯示) + 右箭頭左邊的文字(顯示/不顯示)+ 下分割線
*
* @param iconRes icon圖片
* @param textContent 文字內(nèi)容
*/
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;
}
總結(jié)
文章到此結(jié)束,希望這篇文章對大家有所幫助暖眼,提高開發(fā)效率惕耕。
完整代碼:https://github.com/chaohengxing/MyOneLineView.git