欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义ViewGroup之WaterfallLayout(二)

程序员文章站 2024-03-07 10:25:21
上一篇我们学习了自定义viewgroup的基本步骤,并做了一个customgridlayout的实例,这篇我们继续来说说自定义viewgroup。 android中当有...

上一篇我们学习了自定义viewgroup的基本步骤,并做了一个customgridlayout的实例,这篇我们继续来说说自定义viewgroup。
android中当有大量照片需要展示的时候,我们可以用gridview作为照片墙,但是gridview太整齐了,有时候不规则也是一种美,瀑布流模型就是这样一个不规则的展示墙,接下来我们尝试用自定义viewgroup来实现瀑布流。
实现瀑布流的方式也有很多,下面我们一一道来:

一、继承viewgroup
其实这种实现方式我们只需要在上篇博客的基础上稍作修改即可,主要修改这几个地方:
 •layoutparams
因为瀑布流中每张图片宽度设为相同,高度则会不同,不能通过top加上固定高度得到bottom,所以这里我干脆把四个参数都定义上

public static class layoutparams extends viewgroup.layoutparams {
  public int left = 0;
  public int top = 0;
  public int right = 0;
  public int bottom = 0;

  public layoutparams(context arg0, attributeset arg1) {
  super(arg0, arg1);
  }

  public layoutparams(int arg0, int arg1) {
  super(arg0, arg1);
  }

  public layoutparams(android.view.viewgroup.layoutparams arg0) {
  super(arg0);
  }

 }

 •onmeasure
这里每个图片宽相同,高等比缩放,所以会导致waterfalllayout的layout_height没有用。同时用一个数组top[colums]来记录每列当前高度,以便下次添加图片的时候添加到高度最小的那一列。

 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 int widthmode = measurespec.getmode(widthmeasurespec);
 int sizewidth = measurespec.getsize(widthmeasurespec);
 measurechildren(widthmeasurespec, heightmeasurespec);

 int childcount = this.getchildcount();
 //宽布局为wrap_content时,childwidth取childview宽的最大值,否则动态计算
 if (widthmode == measurespec.at_most) {
  for (int i = 0; i < childcount; i++) {
  view child = this.getchildat(i);
  childwidth = math.max(childwidth, child.getmeasuredwidth());
  }
 } else if (widthmode == measurespec.exactly) {
  childwidth = (sizewidth - (colums - 1) * hspace) / colums;
 } 
 //自定义view的onmeasure、onlayout会执行两次,为了以后执行得到正确的结果
 cleartop();
 //遍历每个子view,将它们坐标保存在它们的layoutparams中,为后面onlayout服务
 for (int i = 0; i < childcount; i++) {
  view child = this.getchildat(i);
  childheight = child.getmeasuredheight() * childwidth / child.getmeasuredwidth();
  layoutparams lparams = (layoutparams) child.getlayoutparams();
  int mincolum = getminheightcolum();
  lparams.left = mincolum * (childwidth + hspace); 
  lparams.top = top[mincolum];
  lparams.right = lparams.left + childwidth;  
  lparams.bottom = lparams.top + childheight;
  top[mincolum] += vspace + childheight;
 }
 //当宽为wrap_content时,计算出的viewgroup宽高
 int wrapwidth;
 int wrapheight;
 if (childcount < colums) {
  wrapwidth = childcount * childwidth + (childcount - 1) * hspace;
 } else {
  wrapwidth = colums * childwidth + (colums - 1) * hspace;
 }
 wrapheight = getmaxheight();

 setmeasureddimension(widthmode == measurespec.at_most? wrapwidth:sizewidth, wrapheight);
 }

 •onlayout
因为layoutparams定义了view的四个参数,所以直接设置即可

 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b) {
 int childcount = this.getchildcount();
 for (int i = 0; i < childcount; i++) {
  view child = this.getchildat(i);
  layoutparams lparams = (layoutparams) child.getlayoutparams();
  child.layout(lparams.left, lparams.top, lparams.right, lparams.bottom);
 }
 }

这里有个地方需要注意一下,每次设置子view的layoutparams前需要将top[]数组清零,因为onmeasure和onlayout会调用两次,这样就确保了下一次设置参数正确。
延伸:为什么自定义viewgroup中的onmeasure和onlayout方法会调用两次?
因为当我们new viewgroup()的时候,通过getwidth()和getheight(),得到的值首先是0,0,然后通过调用onmeasure()和onlayout()方法,会对这个view测量大小,这个时候view的宽高就发生了改变,这个时候又会重新调用一次onmeasure和onlayout方法(当view发生改变的时候,这两个方法会被调用),这时候你通过getwidth和getheight方法就可以看到被测量之后的宽高了。这就是会调用两次的原因。

 •点击事件回调

 //点击事件的回调接口
 public interface onitemclicklistener {
 void onitemclick(view v, int index);
 }

 public void setonitemclicklistener(final onitemclicklistener listener) {
 for (int i = 0; i < getchildcount(); i++) {
  final int index = i;
  view view = getchildat(i);
  view.setonclicklistener(new onclicklistener() {
  @override
  public void onclick(view v) {
   listener.onitemclick(v, index);
  }
  });
 }
 }

使用waterfalllayout来添加图片:

<?xml version="1.0" encoding="utf-8"?>
<scrollview xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.hx.waterfalllayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#303030"
 android:orientation="vertical" >

 <com.hx.waterfalllayout.waterfalllayout
 android:id="@+id/gridview"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:background="#1e1d1d"
 app:hspace="10"
 app:numcolumns="3"
 app:vspace="10" >

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_1" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_2" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_1" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_2" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_1" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_2" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_1" />

 <imageview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaletype="centercrop"
  android:src="@drawable/crazy_2" />
 </com.hx.waterfalllayout.waterfalllayout>
</scrollview>

这里最外层我们用的scrollview,因为照片墙可以无限添加照片,为了让照片数量在超出频幕范围后可以滚动。还有这里imageview都是在xml中写的,当然我们也可以在java中向这个viewgroup动态添加imageview,而且代码更美观。
实现瀑布流图片的点击事件回调函数:

((waterfalllayout) findviewbyid(r.id.waterfalllayout)).setonitemclicklistener(new com.hx.waterfalllayout.waterfalllayout.onitemclicklistener() {
  @override
  public void onitemclick(view v, int index) {
   toast.maketext(mainactivity.this, "item="+index, toast.length_short).show();
  } 
  });

来看看运行效果:

Android自定义ViewGroup之WaterfallLayout(二)

延伸:
一般我们自定义的控件,嵌套在scrollview中会显示不全,这个问题很纠结,不过当你打开scrollview的源码,你会发现有一个地方,同时可以理解scrollview中嵌套viewpager,gridview,listview时候会显示不全的问题了。

Android自定义ViewGroup之WaterfallLayout(二)

这里有个小技巧可以让嵌套的viewpager,gridview,listview显示完全,譬如我们可以定义自己的othergridview继承gridview,并重写onmeasure方法即可,其他viewgroup同理:

public class othergridview extends gridview {

 public othergridview(context paramcontext, attributeset paramattributeset) {
 super(paramcontext, paramattributeset);
 }

 /** 在scrollview内,所以要进行计算高度 */
 @override
 public void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 int expandspec = measurespec.makemeasurespec(integer.max_value >> 2,
  measurespec.at_most);
 super.onmeasure(widthmeasurespec, expandspec);
 }
}

二、继承scrollview
继承scrollview的瀑布流模型当图片过多需要滑动式不必在外面再嵌套一个scrollview。
这时不需要重写onmesure,只需要重写onlayout
 •onlayout 

 /**
 * 进行一些关键性的初始化操作,获取scrollwaterfalllayout的高度,以及得到第一列的宽度值。并在这里开始加载图片
 */
 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b) {
 super.onlayout(changed, l, t, r, b);
 if (changed && !loadonce) {
  firstcolumn = (linearlayout) findviewbyid(r.id.first_column);
  secondcolumn = (linearlayout) findviewbyid(r.id.second_column);
  thirdcolumn = (linearlayout) findviewbyid(r.id.third_column);
  columnwidth = firstcolumn.getwidth();
  loadonce = true;
  loadimages();
 }
 }

 •加载图片

 /**
 * 开始加载图片
 */
 public void loadimages() {
 for (int i = 0; i < imageres.length; i++) {
  bitmap bitmap = resource2bitmap(imageres[i]);
  if (bitmap != null) {
  double ratio = bitmap.getwidth() / (columnwidth * 1.0);
  int scaledheight = (int) (bitmap.getheight() / ratio);
  addimage(i, bitmap, columnwidth, scaledheight);
  }
 }
 }

/**
 * 向imageview中添加一张图片
 * 
 * @param bitmap
 *  待添加的图片
 * @param imagewidth
 *  图片的宽度
 * @param imageheight
 *  图片的高度
 */
 private void addimage(int index, bitmap bitmap, int imagewidth, int imageheight) {
 linearlayout.layoutparams params = new linearlayout.layoutparams(imagewidth, imageheight);
 imageview imageview = new imageview(getcontext());
 imageview.setlayoutparams(params);
 imageview.setimagebitmap(bitmap);
 imageview.setscaletype(scaletype.fit_xy);
 imageview.setpadding(5, 5, 5, 5);
 findcolumntoadd(imageview, imageheight).addview(imageview);
 //给图片添加点击事件的回调
 imageview.setonclicklistener(new onclicklistener() {
  @override
  public void onclick(view v) {
  if (onitemclicklistener != null) {
   onitemclicklistener.onitemclick(v, index);
  }  
  }
 });
 }

 /**
 * 找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。
 * 
 * @param imageview
 * @param imageheight
 * @return 应该添加图片的一列
 */
 private linearlayout findcolumntoadd(imageview imageview,int imageheight) {
 if (firstcolumnheight <= secondcolumnheight) {
  if (firstcolumnheight <= thirdcolumnheight) {
  firstcolumnheight += imageheight;
  return firstcolumn;
  }
  thirdcolumnheight += imageheight;
  return thirdcolumn;
 } else {
  if (secondcolumnheight <= thirdcolumnheight) {
  secondcolumnheight += imageheight;
  return secondcolumn;
  }
  thirdcolumnheight += imageheight;
  return thirdcolumn;
 }
 }

到这里就可以显示瀑布流照片墙了,是不是很方便呢?但是这种方式也有局限性,譬如这里列宽被写死成3列了,没有很好的扩展性。

代码里我们并没有看到自定义viewgroup实现每个childview的layout方法,那么childview是怎么布局的呢?其实childview的布局是通过linearlayout来实现的,也就是说在linearlayout内部调用了每个childview的layout方法,这是不是和之前我们讲自定义view时的组合控件很像呢?

findcolumntoadd(imageview, imageheight).addview(imageview);

 •定义图片点击回调接口

 //点击事件的回调接口
 public onitemclicklistener onitemclicklistener;
 public interface onitemclicklistener {
 void onitemclick(view v, int index);
 }

 public void setonitemclicklistener(onitemclicklistener onitemclicklistener){
 this.onitemclicklistener = onitemclicklistener;
 }

 •使用scrollwaterfalllayout
因为代码里指定了只有三列,所以xml需要三个水平摆放的linearlayout

 <?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">

 <com.hx.waterfalllayout.scrollwaterfalllayout
 android:id="@+id/scrollwaterfalllayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >

 <linearlayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal" >

  <linearlayout
  android:id="@+id/first_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </linearlayout>

  <linearlayout
  android:id="@+id/second_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </linearlayout>

  <linearlayout
  android:id="@+id/third_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </linearlayout>
 </linearlayout>
 </com.hx.waterfalllayout.scrollwaterfalllayout>
</linearlayout>

实现瀑布流图片的点击事件回调函数:

((scrollwaterfalllayout)findviewbyid(r.id.scrollwaterfalllayout)).setonitemclicklistener(new com.hx.waterfalllayout.scrollwaterfalllayout.onitemclicklistener() {
  @override
  public void onitemclick(view v, int index) {
   toast.maketext(mainactivity.this, "item="+index, toast.length_short).show();
  } 
  });

运行效果:

Android自定义ViewGroup之WaterfallLayout(二)

源码下载:http://xiazai.jb51.net/201609/yuanma/android-waterfalllayout(jb51.net).rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。