是否厌倦ListView那单调的一行一行的布局,是否审美疲劳了GridView那九宫格似得布局。急于眼前一亮的笔者昨日就写了一个自定义的控件来实现不规则的控件排列,我取名为“七巧板控件”(笔者也不知道这玩意儿是不是该叫七巧板),首先奉上效果图。
简单介绍一下这个控件的功能:外界对这个控件的所有操作,只需要调用addTangramView方法设置子控件即可,控件内部会自动根据子控件的个数按照给定的“七巧板构造器”实现布局,不过笔者暂时就做了6种模板,对于一般的开发展现是足够了。即使实在万不得已需要自定义排列样式,不用操心,接口我已留好,只需要实现Tangram接口,并且在Adapter类中添加进去,就能自动匹配了。
下面讲解下关键的代码:
首先,是接口的定义
<span style="font-size:14px;">public interface Tangram { //返回整体控件高度与宽度的比例 float getScale(); void layout(TangramLayout tangramLayout ,int unit ,int xStart ,int yStart); }</span>这个接口定义了两个方法,其中getScale方法是指定TangramLayout这个控件的高度与宽度的比例;layout方法就是具体的样式布局实现,unit指的是一个单元的长度,通过上图可知,我默认所有的排列都有一定的比重关系:要么高与宽是1:1,要么是1:2,要么是2:3,这里的unit我默认是给的TangramLayout宽度的三分之一(我也想过自定义比重,但是工作量挺大的,再加上笔者比较懒。。。),这里的xStart指的是X轴方向开始绘制的起点,值其实就是getPaddingLeft计算而来,同理yStart是getPaddingTop计算而来。
然后呢,我把五巧板的实现类代码给出,其他1-6巧板根本不同的排列风格对号入住
<span style="font-size:14px;">public class TangramFiveChild implements Tangram { @Override public float getScale() { return 1.0f; } @Override public void layout(TangramLayout tangramLayout ,int unit ,int xStart ,int yStart) { View child1 = tangramLayout.getChildAt(0); LayoutParams lp1 = child1.getLayoutParams(); lp1.width = 2 * unit; lp1.height = unit; child1.layout(xStart, yStart, xStart + 2 * unit, yStart + unit); View child2 = tangramLayout.getChildAt(1); LayoutParams lp2 = child2.getLayoutParams(); lp2.width = unit; lp2.height = 2 * unit; child2.layout(xStart + 2 * unit, yStart, xStart + 3 * unit, yStart + 2 * unit); View child3 = tangramLayout.getChildAt(2); LayoutParams lp3 = child3.getLayoutParams(); lp3.width = unit; lp3.height = 2 * unit; child3.layout(xStart, yStart + unit, xStart + unit, yStart + 3 * unit); View child4 = tangramLayout.getChildAt(3); LayoutParams lp4 = child4.getLayoutParams(); lp4.width = unit; lp4.height = unit; child4.layout(xStart + unit, yStart + unit, xStart + 2 * unit, yStart + 2 * unit); View child5 = tangramLayout.getChildAt(4); LayoutParams lp5 = child5.getLayoutParams(); lp5.width = 2 * unit; lp5.height = unit; child5.layout(xStart + unit, yStart + 2 * unit, xStart + 3 * unit, yStart + 3 * unit); }</span>大家可以看到,其实就是将5个子控件分别取出来,设置LayoutParams参数,然后再调用layout布局方法定位。这里拿child4举例,child4呢是中间那个正方形的,它的宽度是unit,高度也是unit,它的X起点为xStart+unit,Y起点也为yStart+unit,X终点为xStart+2*unit,Y终点为yStart+2*unit。因此这样就完成了五巧板的定义。
然后呢,是适配器的定义,这个类是动态根据不同子控件数匹配样式的关键
<span style="font-size:14px;">public class TangramAdapter { /** * 根据不同的子控件数,返回不同的构造器 */ public static Tangram getTangram(int count){ switch (count) { case 0: return new TangramZeroChild(); case 1: return new TangramOneChild(); case 2: return new TangramTwoChild(); case 3: return new TangramThreeChild(); case 4: return new TangramFourChild(); case 5: return new TangramFiveChild(); case 6: return new TangramSixChild(); default: return new TangramZeroChild(); } } }</span>代码相当简单,就是对不同的子控件数,返回对应的七巧板实现类。我这里加入了一个TangramZeroChild,作为默认的情况。这个类很好定义,getScale返回0,layout方法置空就行。
最后,就是自定义控件的写法了。
<span style="font-size:14px;">public class TangramLayout extends ViewGroup{ private static final int MAX_CHILDREN = 6; private Tangram tangram; public TangramLayout(Context context){ super(context); initTangram(0); } public TangramLayout(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); initTangram(0); } public TangramLayout(Context context, AttributeSet attrs){ super(context, attrs); initTangram(0); } private void initTangram(int count){ tangram = TangramAdapter.getTangram(count); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ int width = MeasureSpec.getSize(widthMeasureSpec); int widthSpec = MeasureSpec.getMode(widthMeasureSpec); //根据不同的比例,得到不同的值 int height = (int) (tangram.getScale() * width); int heightSpec = MeasureSpec.getMode(heightMeasureSpec); int widthMeasure = MeasureSpec.makeMeasureSpec(width, widthSpec); int heightMeasure = MeasureSpec.makeMeasureSpec(height, heightSpec); //要先测量children,否则不会显示子控件 measureChildren(widthMeasure, heightMeasure); super.onMeasure(widthMeasure, heightMeasure); } /** * 此方法不能调用超过6次 * @param childView 要添加的子布局 */ public void addTangramView(View childView){ if(getChildCount() >= MAX_CHILDREN){ throw new IllegalArgumentException("the max children count is " + MAX_CHILDREN); } addView(childView); initTangram(getChildCount()); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b){ if(getChildCount() > MAX_CHILDREN){ throw new IllegalArgumentException("the max children count is " + MAX_CHILDREN); } int xStart = getPaddingLeft(); int yStart = getPaddingTop(); int unit = (r - l) / 3; tangram.layout(this, unit, xStart, yStart); } }</span>这个自定义控件,其实就4个方法,首先initTangram方法一眼便知,根据子控件数量,得到对应的Tangram实现类。在构造函数中调用一次,在后面的addTangramView方法中调用一次。
onMeasure方法用于计算TangramLayout的宽高,我们将tangram实现类提供的getScale方法的返回值作为高度与宽度的比例进行绘制,这里要注意的是:别忘了调用
<span style="font-size:14px;">measureChildren(widthMeasure, heightMeasure);</span>否则子控件不会绘制,原理大家都明白吧,你都没有给子控件空间,当然不会显示。
addTangramView方法呢,就是设置子控件,然后刷新一下tangram的实现类。这里我因为只实现了6种默认模板,因此在子控件数大于6的时候会抛出异常。亲们若自定义模板的时候别忘了修改这里的界定条件。
最后就是OnLayout的方法了,这里首先也是给出了界定条件。然后计算了xStart与yStart绘制的边界,然后计算了单元格unit的值,这里我默认给的是宽度的三分之一,原因嘛上面提出了,因为我懒。
最后就是怎样来用这个玩意儿了,正如上面所说,全程就只需要调用一个方法:addTangramView方法。下面贴上GIF图里的实现方法,很简单,大家都能看懂。
定义两个xml,一个为子控件的Item,一个为Activity显示的布局,
activity_main.xml:
<span style="font-size:14px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#888888" android:orientation="vertical" android:padding="10dp" > <cc.wxf.tangram.TangramLayout android:id="@+id/tangramLayout" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/moreBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="增加一个子控件" /> </LinearLayout></span>
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/img" style="@style/tangram_img_style" /> <TextView android:id="@+id/txt" style="@style/tangram_text_style" /> </LinearLayout> </span>
<span style="font-size:14px;">public class MainActivity extends Activity{ private TangramLayout tangramLayout; private Button moreBtn; private int current; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tangramLayout = (TangramLayout)findViewById(R.id.tangramLayout); moreBtn = (Button)findViewById(R.id.moreBtn); moreBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v){ switch(current){ case 0: addTangramView(R.color.tangram_red, R.drawable.tab1, "好友"); break; case 1: addTangramView(R.color.tangram_yellow, R.drawable.tab2, "通讯录"); break; case 2: addTangramView(R.color.tangram_pink, R.drawable.tab3, "消息"); break; case 3: addTangramView(R.color.tangram_green, R.drawable.tab4, "通知"); break; case 4: addTangramView(R.color.tangram_blue, R.drawable.tab5, "关注"); break; case 5: addTangramView(R.color.tangram_gray, R.drawable.tab6, "收藏"); break; case 6: Toast.makeText(MainActivity.this, "最多只实现了6个子控件的样式", Toast.LENGTH_SHORT).show(); return; } current++; } }); } private void addTangramView(int backgroundColor ,int imgId ,String text){ View view = View.inflate(this, R.layout.item, null); view.setBackgroundResource(backgroundColor); ImageView img = (ImageView)view.findViewById(R.id.img); img.setImageResource(imgId); TextView txt = (TextView)view.findViewById(R.id.txt); txt.setText(text); tangramLayout.addTangramView(view); } }</span>
点我下下载源码哟