学习android必经之路自定义View
wdx8
8年前
<p>学习Android一定会遇到产品上需要通过自定义View才能实现的控件,或者说为了提高编码效率通过自定View写一个公用的控件方便以后使用。自定义View也是学习Android必须要掌握的知识点之一。本篇文章将分析我在github上看到的一个开源的控件旨在总结一下自己学习自定义View的收获以及通过讲解让自己更加深刻的理解整个实现的过程。</p> <p>这里顺便推荐一款chrome的插件方便在线浏览github叫 Octotree 在chrome应用商店里面可以搜索到</p> <p>Octotree pic使用该插件你就能够通过树形文件结构快速的跳转到你想查看的文件了。</p> <p>简单的介绍一下 BaseItemLayout 项目,就是自定义项目中常用到的列表布局,大家自行脑补微信 我的页面 的列表项。通过自定义该布局方便以后快速的添加此布局提高开发效率。</p> <p>下面我们就来分析如何实现这个自定义View</p> <p>先看一下整体的结构:</p> <p>structure pic</p> <p>在 BaseItemLayout 中管理着许多 ItemView ,所以这里我们其实是需要自定义两个View。一个是 BaseItemLayout 另外一个就是列表项 ItemView 。</p> <h2><strong>自定义 ItemView</strong></h2> <p>ItemView pic</p> <p>如上图, ItemView 中有三个控件--行图标、行标题、箭头。</p> <h3><strong>Step 1 创建ItemView</strong></h3> <p>这里我们创建自定义View将继承 RelativeLayout 。</p> <p>总体结构就是两大部分:</p> <ol> <li>初始化 ItemView 的三个控件。</li> <li>为这些控件添加一些控制样式的方法如:位置、大小、文本颜色…… 代码如下:</li> </ol> <pre> <code class="language-java">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列表项的高度 }</code></pre> <p>上面代码中分别定义好了 icon title arrow 这三个控件在 ItemView 中的位置。</p> <p>在代码的最后添加了一些控制这三个控件的一些方法(此处省略具体代码)。到目前为止,我们就在 ItemView 上面画出来了这三个控件以及规定好了他们大致的位置。</p> <p>在自定义View中,需要重写 ItemView 的构造方法,注意这三个构造方法中的前两个。第一个构造方法是调用的第二个构造方法,第二个则是调用的第三个构造方法。</p> <h3><strong>Step 2 为ItemView新增控制样式的方法</strong></h3> <p>写好了 ItemView 的控件,我们再来添加一些控制其控件样式的方法。</p> <pre> <code class="language-java">/** * 设置图标样式 * * @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); }</code></pre> <p>至此,我们已经创建好的了 ItemView 这个自定义控件。</p> <h3><strong>Step 3 为ItemView添加自定义属性</strong></h3> <p>为了能够在 XML 中通过属性来设置 ItemView 控件的相关属性,就像我们平常用系统中的属性如 android:layout_width="match_parent" 一样我们需要创建一个 attrs 文件来定义需要设置的属性。</p> <p>创建 attrs 属性文件</p> <p>attrs文件结构图</p> <pre> <code class="language-java"><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></code></pre> <p>这里设置了:</p> <ol> <li>ItemView 的行高</li> <li>标题字体的大小以及颜色</li> <li>图标、标题、箭头的位置</li> <li>ItemView 之间分割线的颜色</li> <li>是否显示箭头</li> </ol> <p>OK,到这里我们已经把 ItemView 全部创建好了,现在就可以在你想使用的布局中使用我们自定义的View。</p> <p>但是在实际的开发使用中,我们一般用到这种列表项的布局不会是只画一个,一般来说是多个所以为了更加方便的管理和更加快速的添加。我们又创建一个自定义View BaseItemLayout 来管理多个 ItemView 。</p> <h2><strong>自定义BaseItemLayout</strong></h2> <p>首先我们整理一下思路,我们要在 BaseItemLayout 中做什么事情。</p> <ol> <li>提供设置 ItemView 样式参数的set方法。</li> <li>绘制并把 ItemView 添加到 BaseItemLayout 中。</li> </ol> <h3>Step 1 初始化BaseItemLayout</h3> <p>同样的步骤我们首先要对 BaseItemLayout 初始化</p> <pre> <code class="language-java">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() 设置图标的宽度 }</code></pre> <p>注意 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ItemAttrs);</p> <p>得到当前 BaseItemLayout 的属性数组,之后我们在 XML 中设置的值就可以作为参数在方法中使用。</p> <p>特别提醒别忘记 a.recycle();</p> <h3><strong>Step 2 绘制BaseItemLayout,把ItemView添加到BaseItemLayout中</strong></h3> <p>步骤:</p> <ol> <li>设置好 ItemView 的控件样式。</li> <li>依次把 ItemView 添加到 BaseItemLayout 中。</li> </ol> <pre> <code class="language-java">/** * 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); } }</code></pre> <p>这里通过</p> <p>setIconStyle() setTitleStyle() setArrowStyle() setItemLayoutParams()</p> <p>这四个方法设置好 ItemView 中控件的样式。</p> <p>然后通过 addItem() 方法将 ItemView 添加到 BaseItemLayout</p> <p>addItem() 方法:</p> <pre> <code class="language-java">/** * 添加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的下面的分割线 }</code></pre> <p>这里按照 上分割线 -> itemView -> 下分割线 的顺序绘制。这样就把 ItemView 绘制到 BaseItemLayout 中。</p> <p>剩下的就是提供图片、文字、箭头的方法,设置样式的方法,此处省略。</p> <p>设置样式的方法</p> <p>至此,已经完成了 BaseItemLayout 的自定义。最后我们来看一下如何使用自定义的View</p> <h2><strong>使用方法</strong></h2> <pre> <code class="language-java">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(); } }</code></pre> <p>整个自定义View的流程就是这么多了,其中一些具体的方法需要读者自行去阅读。总体来说自定义View的步骤:</p> <ol> <li>写好自定义View的布局(也可以省略,在java中绘制出来)。</li> <li>创建自定义View, 设置好相对应的方法。</li> </ol> <p> </p> <p>来自:http://jngoogle.farbox.com/post/android/view/2016-11-17</p> <p> </p>