Android自定义ViewGroup之WaterfallLayout(二)
作者:huaxun66 发布时间:2022-10-11 01:58:02
上一篇我们学习了自定义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


猜你喜欢
- volatile先看个例子class Test {// 定义一个全局变量 private boolean isRu
- 一、 lib文件的简介.lib是一种文件后缀,是Windows操作系统的库文件,有静态lib和动态lib之分:1)、静态lib文件
- Docker 存储驱动详细介绍最近做项目,期间对Docker 存储驱动不会,于是在网上找资料,并解决了,这里就记录下。目的理解docker的
- 封装(Encapsulation)是面向对象编程的一个核心概念,它意味着将数据(属性)和方法(操作数据的函数)捆绑在一起,形成一个类(Cla
- 概述Spring Boot简化了Spring应用的开发过程,遵循约定优先配置的原则提供了各类开箱即用(out-of-the-box)的框架配
- 附加依赖项属性是一个属性本来不属于对象自己,但是某些特定场景其他的对象要使用该对象在这种场景下的值。这个值只在这个场景下使用。基于这个需求设
- 一、java方法重写方法的重写是子类根据需求对父类继承的方法进行重新的编写,在重写时,可以使用super方法的方式来保留父类中的方法,注意:
- Threadlocal有什么用:简单的说就是,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自
- maven3 安装:安装 Maven 之前要求先确定你的 JDK 已经安装配置完成。Maven是 Apache 下的一个项目,目前最新版本是
- 前言大富翁,又名地产大亨。是一种多人策略图版游戏。参与者分得游戏金钱,凭运气(掷骰子)及交易策略,买地、建楼以赚取租金。英文原名monopo
- maven打包方式使用maven打包插件maven-jar-plugin在pom.xml文件最后新增以下代码。maven-dependenc
- 原文:http://it.deepinmind.com/java/2015/03/17/20-examples-of-date-and-ti
- 本文实例讲述了C#简单实现显示中文格式星期几的方法。分享给大家供大家参考,具体如下:1.DateTime.Now.ToString(&quo
- 最近单位又有一个新Java项目。涉及到扫码登录。之前项目使用的是 ajax轮询的方式。感觉太low了。所以这次用webSocket的方式进行
- 一.关于使用Mybatisplus自带的selectById和insert方法时的一些问题1.selectById的问题(1).表的主键列名
- 本文实例为大家分享了C#实现餐厅管理系统的具体代码,供大家参考,具体内容如下部分代码:fm_change_password.csusing
- 我们知道,值类型的变量是在堆栈上分配内存的,而引用类型包括System.Object的对象是在堆上分配内存的,基于这一特点,当值类型被类型转
- Java如何实现线程中断?通过调用Thread类的实例方法interrupt。如下:Thread thread = new Thread()
- 这篇文章中我们来继续学习Picasso中还提供了哪些扩展功能,一个好的框架一定是扩展性强的,你需要的我刚好有。下面看一下都提供了哪些扩展功能
- 本文主要介绍了关于c#和java base64不一致的解决方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧不一致的问题不