學習android必經之路自定義View
學習Android一定會遇到產品上需要通過自定義View才能實現的控件,或者說為了提高編碼效率通過自定View寫一個公用的控件方便以后使用。自定義View也是學習Android必須要掌握的知識點之一。本篇文章將分析我在github上看到的一個開源的控件旨在總結一下自己學習自定義View的收獲以及通過講解讓自己更加深刻的理解整個實現的過程。
這里順便推薦一款chrome的插件方便在線瀏覽github叫 Octotree 在chrome應用商店里面可以搜索到
Octotree pic使用該插件你就能夠通過樹形文件結構快速的跳轉到你想查看的文件了。
簡單的介紹一下 BaseItemLayout 項目,就是自定義項目中常用到的列表布局,大家自行腦補微信 我的頁面 的列表項。通過自定義該布局方便以后快速的添加此布局提高開發效率。
下面我們就來分析如何實現這個自定義View
先看一下整體的結構:
structure pic
在 BaseItemLayout 中管理著許多 ItemView ,所以這里我們其實是需要自定義兩個View。一個是 BaseItemLayout 另外一個就是列表項 ItemView 。
自定義 ItemView
ItemView pic
如上圖, ItemView 中有三個控件--行圖標、行標題、箭頭。
Step 1 創建ItemView
這里我們創建自定義View將繼承 RelativeLayout 。
總體結構就是兩大部分:
- 初始化 ItemView 的三個控件。
- 為這些控件添加一些控制樣式的方法如:位置、大小、文本顏色…… 代碼如下:
public class ItemView extends RelativeLayout {
private Context context;
private ImageView icon;
private TextView title;
private ImageView arrow;
private LayoutParams iconLp;
private LayoutParams titleLp;
private LayoutParams arrowLp;
public ItemView(Context context) {
this(context, null);
}
public ItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public void initView(Context context) {
this.context = context;
icon = new ImageView(context);
title = new TextView(context);
arrow = new ImageView(context);
iconLp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
iconLp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
addView(icon, iconLp);
titleLp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
titleLp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
titleLp.addRule(RelativeLayout.RIGHT_OF, R.id.iv_icon);
addView(title, titleLp);
arrowLp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
arrowLp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
arrowLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
addView(arrow, arrowLp);
}
//---------為控件添加一些控制其屬性的方法----------
//設置控件的相關屬性(icon title arrow)
//setIconStyle() setTitleStyle() setArrowStyle()
//setLayoutParams() 設置ItemView列表項的高度
}
上面代碼中分別定義好了 icon title arrow 這三個控件在 ItemView 中的位置。
在代碼的最后添加了一些控制這三個控件的一些方法(此處省略具體代碼)。到目前為止,我們就在 ItemView 上面畫出來了這三個控件以及規定好了他們大致的位置。
在自定義View中,需要重寫 ItemView 的構造方法,注意這三個構造方法中的前兩個。第一個構造方法是調用的第二個構造方法,第二個則是調用的第三個構造方法。
Step 2 為ItemView新增控制樣式的方法
寫好了 ItemView 的控件,我們再來添加一些控制其控件樣式的方法。
/**
* 設置圖標樣式
*
* @param iconMarginLeft 圖標距離itemView左邊的距離
* @param resId 圖標資源
* @param height 圖標的高度
* @param width 圖標的寬度
*/
public void setIconStyle(int iconMarginLeft, int resId, int height, int width) {
// set icon margin left itemLayout
iconLp.leftMargin = DensityUtil.dip2px(context, iconMarginLeft);
// set pic for icon
icon.setImageResource(resId);
// set icon dimen
ViewGroup.LayoutParams layoutParams = icon.getLayoutParams();
layoutParams.height = height;
layoutParams.width = width;
icon.setLayoutParams(layoutParams);
}
/**
* 設置標題樣式
*
* @param titleMarginLeft 標題距離圖標左邊的距離
* @param text 標題的內容
* @param textColor 標題的顏色
* @param textSize 標題文字的大小
*/
public void setTitleStyle(int titleMarginLeft, String text, int textColor, int textSize) {
// set title margin left
titleLp.leftMargin = DensityUtil.dip2px(context, titleMarginLeft);
// set title size and content
title.setText(text);
title.setTextColor(textColor);
title.setTextSize(DensityUtil.dip2px(context, textSize));
}
/**
* 設置箭頭的樣式
*
* @param isShow 是否顯示箭頭
* @param arrowMarginRight 箭頭距離ItemView右邊的距離
* @param resId 箭頭圖片資源
* @param height 箭頭的高度
* @param width 箭頭的寬度
*/
public void setArrowStyle(boolean isShow, int arrowMarginRight, int resId, int height, int width) {
// if show arrow
if (isShow) {
arrow.setVisibility(VISIBLE);
} else {
arrow.setVisibility(GONE);
}
arrowLp.rightMargin = DensityUtil.dip2px(context, arrowMarginRight);
// set image resource and dimen
arrow.setImageResource(resId);
ViewGroup.LayoutParams layoutParams = arrow.getLayoutParams();
layoutParams.height = height;
layoutParams.width = width;
arrow.setLayoutParams(layoutParams);
}
/**
* set ItemView height
* @param itemHeight
*/
public void setItemLayoutParams(int itemHeight) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.height = DensityUtil.dip2px(context, itemHeight);
setLayoutParams(layoutParams);
}
至此,我們已經創建好的了 ItemView 這個自定義控件。
Step 3 為ItemView添加自定義屬性
為了能夠在 XML 中通過屬性來設置 ItemView 控件的相關屬性,就像我們平常用系統中的屬性如 android:layout_width="match_parent" 一樣我們需要創建一個 attrs 文件來定義需要設置的屬性。
創建 attrs 屬性文件
attrs文件結構圖
<resources>
<declare-styleable name="ItemAttrs">
<attr name="item_height_jngoogle" format="integer"/>
<attr name="text_size_jngoogle" format="integer"/>
<attr name="text_color_jngoogle" format="color"/>
<attr name="icon_marginLeft_jngoogle" format="integer"/>
<attr name="title_marginLeft_jngoogle" format="integer"/>
<attr name="arrow_marginRight_jngoogle" format="integer"/>
<attr name="lineColor_jngoogle" format="color"/>
<attr name="isShow_jngoogle" format="boolean"/>
</declare-styleable>
這里設置了:
- ItemView 的行高
- 標題字體的大小以及顏色
- 圖標、標題、箭頭的位置
- ItemView 之間分割線的顏色
- 是否顯示箭頭
OK,到這里我們已經把 ItemView 全部創建好了,現在就可以在你想使用的布局中使用我們自定義的View。
但是在實際的開發使用中,我們一般用到這種列表項的布局不會是只畫一個,一般來說是多個所以為了更加方便的管理和更加快速的添加。我們又創建一個自定義View BaseItemLayout 來管理多個 ItemView 。
自定義BaseItemLayout
首先我們整理一下思路,我們要在 BaseItemLayout 中做什么事情。
- 提供設置 ItemView 樣式參數的set方法。
- 繪制并把 ItemView 添加到 BaseItemLayout 中。
Step 1 初始化BaseItemLayout
同樣的步驟我們首先要對 BaseItemLayout 初始化
public class BaseItemLayout extends LinearLayout {
private Context context;
private List<Integer> iconList = new ArrayList<>();
private List<String> titleList = new ArrayList<>();
//init icon title arrow attribute
private int iconHeight = 24;
private int iconWidth = 24;
private int iconMarginLeft = 10;
private int textSize = 14; // 14dp
private int textColor = 0xFF666666;
private int titleMarginLeft = 10; // 10dp
private int arrowResId = 0;
private int arrowHeight = 24;
private int arrowWidth = 16;
private int arrowMarginRight = 10;
private boolean isShow = false; // set arrow is show
// divide line color
private int lineColor = 0xff303F9F;
// item height
private int itemHeight = 40;
// distance between items
private SparseArray<Integer> itemsMarginArray = new SparseArray<>();
private static int DEFAOUT_ITEMS_MARGIN = 10;
private static int ZERO_ITEMS_MARGIN = 0;
public BaseItemLayout(Context context) {
this(context, null);
}
public BaseItemLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ItemAttrs);
iconMarginLeft = a.getInteger(R.styleable.ItemAttrs_icon_marginLeft_jngoogle, iconMarginLeft);
textSize = a.getInteger(R.styleable.ItemAttrs_text_size_jngoogle, textSize);
textColor = a.getInteger(R.styleable.ItemAttrs_text_color_jngoogle, textColor);
titleMarginLeft = a.getInteger(R.styleable.ItemAttrs_title_marginLeft_jngoogle, titleMarginLeft);
arrowMarginRight = a.getInteger(R.styleable.ItemAttrs_arrow_marginRight_jngoogle, arrowMarginRight);
lineColor = a.getInteger(R.styleable.ItemAttrs_lineColor_jngoogle, lineColor);
itemHeight = a.getInteger(R.styleable.ItemAttrs_item_height_jngoogle, itemHeight);
isShow = a.getBoolean(R.styleable.ItemAttrs_isShow_jngoogle, isShow);
a.recycle();
init(context);
}
// create()方法 -- 繪制BaseItemLayout
// addItem()方法 -- 添加 itemView 到 BaseItemLayout 中
//一些參數值得set方法
//ex:setIconWidth() 設置圖標的寬度
}
注意 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ItemAttrs);
得到當前 BaseItemLayout 的屬性數組,之后我們在 XML 中設置的值就可以作為參數在方法中使用。
特別提醒別忘記 a.recycle();
Step 2 繪制BaseItemLayout,把ItemView添加到BaseItemLayout中
步驟:
- 設置好 ItemView 的控件樣式。
- 依次把 ItemView 添加到 BaseItemLayout 中。
/**
* create baseItemLayout
*/
public void create() {
if (iconList.isEmpty()) {
throw new RuntimeException("iconList is null");
}
if (titleList.isEmpty()) {
throw new RuntimeException("titleList is null");
}
if (iconList.size() != titleList.size()) {
throw new RuntimeException("params not match, icon's sum should be equal title's sum");
}
for (int i = 0; i < iconList.size(); i++) {
ItemView itemView = new ItemView(context);
itemView.setIconStyle(iconMarginLeft, iconList.get(i), iconHeight, iconWidth);
itemView.setTitleStyle(titleMarginLeft, titleList.get(i), textColor, textSize);
itemView.setArrowStyle(isShow, arrowMarginRight, arrowResId, arrowHeight, arrowWidth);
itemView.setItemLayoutParams(itemHeight);// set item height
addItem(itemView, i);
}
}
這里通過
setIconStyle() setTitleStyle() setArrowStyle() setItemLayoutParams()
這四個方法設置好 ItemView 中控件的樣式。
然后通過 addItem() 方法將 ItemView 添加到 BaseItemLayout
addItem() 方法:
/**
* 添加ItemView布局到baseItemLayout
*
* @param itemView
* @param pos the position of current itemView
*/
private void addItem(ItemView itemView, int pos) {
if (itemsMarginArray.get(pos) != null) {
if (itemsMarginArray.get(pos) > 0) {
addView(createLineView(itemsMarginArray.get(pos)));
}
} else {
addView(createLineView(DEFAOUT_ITEMS_MARGIN));
}
addView(itemView);
addView(createLineView(ZERO_ITEMS_MARGIN));// item的下面的分割線
}
這里按照 上分割線 -> itemView -> 下分割線 的順序繪制。這樣就把 ItemView 繪制到 BaseItemLayout 中。
剩下的就是提供圖片、文字、箭頭的方法,設置樣式的方法,此處省略。
設置樣式的方法
至此,已經完成了 BaseItemLayout 的自定義。最后我們來看一下如何使用自定義的View
使用方法
public class MainActivity extends AppCompatActivity {
private BaseItemLayout baseItemLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
baseItemLayout = (BaseItemLayout) findViewById(R.id.base_item_layout);
initData();
}
private void initData() {
List<Integer> iconList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
titleList.add("photo");
titleList.add("favorite");
titleList.add("wallet");
titleList.add("cardCase");
titleList.add("settings");
iconList.add(R.mipmap.xc);
iconList.add(R.mipmap.sc);
iconList.add(R.mipmap.qb);
iconList.add(R.mipmap.kb);
iconList.add(R.mipmap.sz);
baseItemLayout.setTitleList(titleList)
.setIconList(iconList)
.setArrowIsShow(true)
.setArrowResId(R.mipmap.img_find_arrow)
.setItemsMargin(0,0)
.create();
}
}
整個自定義View的流程就是這么多了,其中一些具體的方法需要讀者自行去閱讀。總體來說自定義View的步驟:
- 寫好自定義View的布局(也可以省略,在java中繪制出來)。
- 創建自定義View, 設置好相對應的方法。
來自:http://jngoogle.farbox.com/post/android/view/2016-11-17