Android自定义ViewGroup之WaterfallLayout(二)_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();
  }
  });

来看看运行效果:

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

这里有个小技巧可以让嵌套的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();
  }
  });

运行效果:

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

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, viewgroup
WaterfallLayout
viewgroup layout、tablayout 自定义view、自定义view加载layout、自定义view layout、自定义viewgroup,以便于您获取更多的相关知识。

时间: 2024-10-28 07:12:02

Android自定义ViewGroup之WaterfallLayout(二)_Android的相关文章

Android自定义ViewGroup的实现方法_Android

     在android中提供了常见的几种ViewGroup的实现,包括LinearLayout.Relativeayout.FrameLayout等.这些ViewGroup可以满足我们一般的开发需求,但是对于界面要求复杂的,这几个布局就显得捉襟见肘了.所以自定义的ViewGroup在我们接触过的应用中比比皆是.      要想实现一个自定义的ViewGroup,第一步是学会自定义属性,这些自定义的属性将让我们配置布局文件的时候更加的灵活.自定义属性是在value目录下声明一个attrs.xm

Android自定义ViewGroup实现绚丽的仿支付宝咻一咻雷达脉冲效果_Android

去年春节的时候支付宝推行的集福娃活动着实火的不能再火了,更给力的是春晚又可以全民参与咻一咻集福娃活动,集齐五福就可平分亿元大红包,只可惜没有敬业福--那时候在家没事写了个咻一咻插件,只要到了咻一咻的时间点插件就可以自动的点击咻一咻来咻红包,当时只是纯粹练习这部分技术代码没有公开,后续计划写篇关于插件这方面的文章,扯远了(*^__^*) --我们知道在支付宝的咻一咻页面有个雷达扩散的动画效果,当时感觉动画效果非常棒,于是私下尝试着实现了类似的效果,后来在github发现有大神也写有类似效果,于是读

Android自定义ViewGroup打造各种风格的SlidingMenu_Android

上篇给大家介绍QQ5.0侧滑菜单的视频课程,对于侧滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉,目前可以实现任意效果的侧滑菜单了,感谢鸿洋大大!! 用的是HorizontalScrollView来实现的侧滑菜单功能,HorizontalScrollView的好处是为我们解决了滑动功能,处理了滑动冲突问题,让我们使用起来非常方便,但是滑动和冲突处理都是android中的难点,是我们应该掌握的知识点,掌握了这些,我们可以不依赖于系统的API,随心所欲打造我们想要的效果,因此这篇文章我将直接

Android自定义ViewGroup实现标签流容器FlowLayout_Android

本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习.和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方. 按照惯例,我们先来看看效果图 一.写代码之前,有几个是问题是我们先要弄清楚的: 1.什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许

Android自定义ViewGroup打造各种风格的SlidingMenu

上篇给大家介绍QQ5.0侧滑菜单的视频课程,对于侧滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉,目前可以实现任意效果的侧滑菜单了,感谢鸿洋大大!! 用的是HorizontalScrollView来实现的侧滑菜单功能,HorizontalScrollView的好处是为我们解决了滑动功能,处理了滑动冲突问题,让我们使用起来非常方便,但是滑动和冲突处理都是android中的难点,是我们应该掌握的知识点,掌握了这些,我们可以不依赖于系统的API,随心所欲打造我们想要的效果,因此这篇文章我将直接

viewgroup 传参-android自定义ViewGroup的问题

问题描述 android自定义ViewGroup的问题 在名为Demo的activity中用到了一个继承ViewGroup的类MyView来布局,具体是这样的 在Demo的布局文件xml中, 在Demo的代码中 MyView scroll = (MyView) findViewById(R.id.view1); 在MyView中的构造函数 public MyView(Context context, AttributeSet attrs) { //各类操作 } 问题是Demo需要给scroll传

Android自定义ViewGroup实现带箭头的圆角矩形菜单_Android

本文和大家一起做一个带箭头的圆角矩形菜单,大概长下面这个样子:  要求顶上的箭头要对准菜单锚点,菜单项按压反色,菜单背景色和按压色可配置. 最简单的做法就是让UX给个三角形的图片往上一贴,但是转念一想这样是不是太low了点,而且不同分辨率也不太好适配,干脆自定义一个ViewGroup吧! 自定义ViewGroup其实很简单,基本都是按一定的套路来的.  一.定义一个attrs.xml就是声明一下你的这个自定义View有哪些可配置的属性,将来使用的时候可以自由配置.这里声明了7个属性,分别是:箭头

Android自定义ViewGroup之实现FlowLayout流式布局_Android

整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/  一.FlowLayout介绍  所谓FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行.有点像所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局.Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图:  gi

Android自定义ViewGroup实现受边界限制的滚动操作(3)_Android

上一篇文章<自定义viewgroup(2)>地址:http://www.jb51.net/article/100610.htm 代码 package com.example.libingyuan.horizontallistview.ScrollViewGroup; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; import androi