Android自定義ViewGroup之WaterfallLayout(二)
上一篇我們學(xué)習(xí)了自定義ViewGroup的基本步驟,并做了一個(gè)CustomGridLayout的實(shí)例,這篇我們繼續(xù)來(lái)說(shuō)說(shuō)自定義ViewGroup。
Android中當(dāng)有大量照片需要展示的時(shí)候,我們可以用GridView作為照片墻,但是GridView太整齊了,有時(shí)候不規(guī)則也是一種美,瀑布流模型就是這樣一個(gè)不規(guī)則的展示墻,接下來(lái)我們嘗試用自定義ViewGroup來(lái)實(shí)現(xiàn)瀑布流。
實(shí)現(xiàn)瀑布流的方式也有很多,下面我們一一道來(lái):
一、繼承ViewGroup
其實(shí)這種實(shí)現(xiàn)方式我們只需要在上篇博客的基礎(chǔ)上稍作修改即可,主要修改這幾個(gè)地方:
•LayoutParams
因?yàn)槠俨剂髦忻繌垐D片寬度設(shè)為相同,高度則會(huì)不同,不能通過(guò)top加上固定高度得到bottom,所以這里我干脆把四個(gè)參數(shù)都定義上
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
這里每個(gè)圖片寬相同,高等比縮放,所以會(huì)導(dǎo)致WaterfallLayout的layout_height沒(méi)有用。同時(shí)用一個(gè)數(shù)組top[colums]來(lái)記錄每列當(dāng)前高度,以便下次添加圖片的時(shí)候添加到高度最小的那一列。
@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時(shí),childWidth取childView寬的最大值,否則動(dòng)態(tài)計(jì)算 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會(huì)執(zhí)行兩次,為了以后執(zhí)行得到正確的結(jié)果 clearTop(); //遍歷每個(gè)子view,將它們坐標(biāo)保存在它們的LayoutParams中,為后面onLayout服務(wù) 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; } //當(dāng)寬為wrap_content時(shí),計(jì)算出的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
因?yàn)長(zhǎng)ayoutParams定義了View的四個(gè)參數(shù),所以直接設(shè)置即可
@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); } }
這里有個(gè)地方需要注意一下,每次設(shè)置子View的LayoutParams前需要將top[]數(shù)組清零,因?yàn)閛nMeasure和onLayout會(huì)調(diào)用兩次,這樣就確保了下一次設(shè)置參數(shù)正確。
延伸:為什么自定義viewGroup中的onMeasure和onLayout方法會(huì)調(diào)用兩次?
因?yàn)楫?dāng)我們new ViewGroup()的時(shí)候,通過(guò)getWidth()和getHeight(),得到的值首先是0,0,然后通過(guò)調(diào)用onMeasure()和onLayout()方法,會(huì)對(duì)這個(gè)view測(cè)量大小,這個(gè)時(shí)候view的寬高就發(fā)生了改變,這個(gè)時(shí)候又會(huì)重新調(diào)用一次onMeasure和onLayout方法(當(dāng)view發(fā)生改變的時(shí)候,這兩個(gè)方法會(huì)被調(diào)用),這時(shí)候你通過(guò)getWidth和getHeight方法就可以看到被測(cè)量之后的寬高了。這就是會(huì)調(diào)用兩次的原因。
•點(diǎn)擊事件回調(diào)
//點(diǎn)擊事件的回調(diào)接口 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來(lái)添加圖片:
<?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,因?yàn)檎掌瑝梢詿o(wú)限添加照片,為了讓照片數(shù)量在超出頻幕范圍后可以滾動(dòng)。還有這里ImageView都是在xml中寫的,當(dāng)然我們也可以在Java中向這個(gè)ViewGroup動(dòng)態(tài)添加ImageView,而且代碼更美觀。
實(shí)現(xiàn)瀑布流圖片的點(diǎn)擊事件回調(diào)函數(shù):
((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(); } });
來(lái)看看運(yùn)行效果:
延伸:
一般我們自定義的控件,嵌套在scrollview中會(huì)顯示不全,這個(gè)問(wèn)題很糾結(jié),不過(guò)當(dāng)你打開scrollview的源碼,你會(huì)發(fā)現(xiàn)有一個(gè)地方,同時(shí)可以理解scrollview中嵌套viewpager,gridview,listview時(shí)候會(huì)顯示不全的問(wèn)題了。
這里有個(gè)小技巧可以讓嵌套的viewpager,gridview,listview顯示完全,譬如我們可以定義自己的OtherGridView繼承Gridview,并重寫onMeasure方法即可,其他ViewGroup同理:
public class OtherGridView extends GridView { public OtherGridView(Context paramContext, AttributeSet paramAttributeSet) { super(paramContext, paramAttributeSet); } /** 在ScrollView內(nèi),所以要進(jìn)行計(jì)算高度 */ @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的瀑布流模型當(dāng)圖片過(guò)多需要滑動(dòng)式不必在外面再嵌套一個(gè)ScrollView。
這時(shí)不需要重寫onMesure,只需要重寫onLayout
•onLayout
/** * 進(jìn)行一些關(guān)鍵性的初始化操作,獲取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); //給圖片添加點(diǎn)擊事件的回調(diào) imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { onItemClickListener.onItemClick(v, index); } } }); } /** * 找到此時(shí)應(yīng)該添加圖片的一列。原則就是對(duì)三列的高度進(jìn)行判斷,當(dāng)前高度最小的一列就是應(yīng)該添加的一列。 * * @param imageView * @param imageHeight * @return 應(yīng)該添加圖片的一列 */ 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列了,沒(méi)有很好的擴(kuò)展性。
代碼里我們并沒(méi)有看到自定義ViewGroup實(shí)現(xiàn)每個(gè)childView的layout方法,那么childView是怎么布局的呢?其實(shí)childView的布局是通過(guò)LinearLayout來(lái)實(shí)現(xiàn)的,也就是說(shuō)在LinearLayout內(nèi)部調(diào)用了每個(gè)childView的layout方法,這是不是和之前我們講自定義View時(shí)的組合控件很像呢?
findColumnToAdd(imageView, imageHeight).addView(imageView);
•定義圖片點(diǎn)擊回調(diào)接口
//點(diǎn)擊事件的回調(diào)接口 public OnItemClickListener onItemClickListener; public interface OnItemClickListener { void onItemClick(View v, int index); } public void setOnItemClickListener(OnItemClickListener onItemClickListener){ this.onItemClickListener = onItemClickListener; }
•使用ScrollWaterfallLayout
因?yàn)榇a里指定了只有三列,所以xml需要三個(gè)水平擺放的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>
實(shí)現(xiàn)瀑布流圖片的點(diǎn)擊事件回調(diào)函數(shù):
((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(); } });
運(yùn)行效果:
源碼下載:http://xiazai.jb51.net/201609/yuanma/Android-WaterfallLayout(jb51.net).rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android自定義ViewGroup之實(shí)現(xiàn)FlowLayout流式布局
- Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法
- Android實(shí)現(xiàn)熱門標(biāo)簽的流式布局
- 解析在Android中為TextView增加自定義HTML標(biāo)簽的實(shí)現(xiàn)方法
- android配合viewpager實(shí)現(xiàn)可滑動(dòng)的標(biāo)簽欄示例分享
- Android中使用include標(biāo)簽和merge標(biāo)簽重復(fù)使用布局
- Android開發(fā)常用標(biāo)簽小結(jié)
- android多行標(biāo)簽熱點(diǎn)示例
- Android自定義ViewGroup之CustomGridLayout(一)
- Android自定義ViewGroup之FlowLayout(三)
相關(guān)文章
Android Moveview滑屏移動(dòng)視圖類完整實(shí)例
這篇文章主要介紹了Android Moveview滑屏移動(dòng)視圖類,很有實(shí)用價(jià)值,需要的朋友可以參考下2014-07-07Android部分手機(jī)拍照后獲取的圖片被旋轉(zhuǎn)問(wèn)題的解決方法
這篇文章主要為大家詳細(xì)介紹了Android部分手機(jī)拍照后獲取的圖片被旋轉(zhuǎn)問(wèn)題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Android開發(fā)實(shí)現(xiàn)Gallery畫廊效果的方法
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)Gallery畫廊效果的方法,結(jié)合具體實(shí)例形式分析了Android使用Gallery實(shí)現(xiàn)畫廊功能的具體操作技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-06-06Flutter 用自定義轉(zhuǎn)場(chǎng)動(dòng)畫實(shí)現(xiàn)頁(yè)面切換
本篇介紹了 fluro 導(dǎo)航到其他頁(yè)面的自定義轉(zhuǎn)場(chǎng)動(dòng)畫實(shí)現(xiàn),F(xiàn)lutter本身提供了不少預(yù)定義的轉(zhuǎn)場(chǎng)動(dòng)畫,可以通過(guò) transitionBuilder 參數(shù)設(shè)計(jì)多種多樣的轉(zhuǎn)場(chǎng)動(dòng)畫,也可以通過(guò)自定義的 AnimatedWidget實(shí)現(xiàn)個(gè)性化的轉(zhuǎn)場(chǎng)動(dòng)畫效果。2021-06-06Android ListView與getView調(diào)用卡頓問(wèn)題解決辦法
這篇文章主要介紹了Android ListView與getView調(diào)用卡頓問(wèn)題解決辦法的相關(guān)資料,這里提供實(shí)例及解決辦法幫助大家解決這種問(wèn)題,需要的朋友可以參考下2017-08-08Android?使用maven?publish插件發(fā)布產(chǎn)物(aar)流程實(shí)踐
這篇文章主要介紹了Android?使用maven?publish插件發(fā)布產(chǎn)物(aar)流程實(shí)踐,Android?Gradle插件根據(jù)項(xiàng)目gradle中應(yīng)用不同的插件類型在編譯組裝后會(huì)生成不同的產(chǎn)物,具體相關(guān)介紹,需要的小伙伴可以參考一下2022-09-09Android實(shí)戰(zhàn)教程第四十篇之Chronometer實(shí)現(xiàn)倒計(jì)時(shí)
這篇文章主要介紹了Android實(shí)戰(zhàn)教程第四十篇之Chronometer實(shí)現(xiàn)倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android仿Keep運(yùn)動(dòng)休息倒計(jì)時(shí)圓形控件
這篇文章主要為大家詳細(xì)介紹了Android仿Keep運(yùn)動(dòng)休息倒計(jì)時(shí)圓形控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09Android編程之界面跳動(dòng)提示動(dòng)畫效果實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程之界面跳動(dòng)提示動(dòng)畫效果實(shí)現(xiàn)方法,實(shí)例分析了Android動(dòng)畫效果的布局及功能相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11