日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Android FlowLayout流式布局實現詳解

瀏覽:41日期:2022-09-22 14:27:46

本文實例為大家分享了Android FlowLayout流式布局的具體代碼,供大家參考,具體內容如下

最近使用APP的時候經??吹接?/p>

Android FlowLayout流式布局實現詳解

這種流式布局 ,今天我就跟大家一起來動手擼一個這種自定義控件.

首先說一下自定義控件的流程:

自定義控件一般要么繼承View要么繼承ViewGroup

View的自定義流程:

繼承一個View-->重寫onMeasure方法-->重寫onDraw方法-->定義自定義屬性-->處理手勢操作

ViewGroup的自定義流程:

繼承一個ViewGroup-->重寫onMeasure方法-->重寫onLayout-->重寫onDraw方法->定義自定義屬性-->處理手勢操作

我們可以看到自定義View和自定義ViewGroup略微有些不同,自定義ViewGroup多了個onlayout方法,那么這些方法都有什么作用呢?這里由于篇幅的問題不做過多的描述,簡單的說

onMeasure:用來計算,計算自身顯示在頁面上的大小

onLayout:用來計算子View擺放的位置,因為View已經是最小單元了,所以沒有字View,所以沒有onLayout方法

onDraw:用來繪制你想展示的東西

定義自定義屬性就是暴露一些屬性給外部調用

好了,了解了自定義View的基本自定義流程,我們可以知道我們應該需要自定義一個ViewGroup就可以滿足該需求.

首先自定義一個View命名為FlowLayout繼承ViewGroup

public class FlowLayout extends ViewGroup { public FlowLayout(Context context) { this(context,null); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs,0); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).layout(l,t,r,b); } }}

可以看到onLayout是必須重寫的,不然系統不知道你這個ViewGroup的子View擺放的位置.

然后XML中引用

<test.hxy.com.testflowlayout.FlowLayout xmlns:android='http://schemas.android.com/apk/res/android' xmlns:app='http://schemas.android.com/apk/res-auto' xmlns:tools='http://schemas.android.com/tools' android: android:layout_margin='10dp' android:layout_width='match_parent' android:layout_height='match_parent' />

Activity中設置數據

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FlowLayout mFlowLayout = (FlowLayout) findViewById(R.id.mFlowLayout); List<String> list = new ArrayList<>(); list.add('java'); list.add('javaEE'); list.add('javaME'); list.add('c'); list.add('php'); list.add('ios'); list.add('c++'); list.add('c#'); list.add('Android'); for (int i = 0; i < list.size(); i++) { View inflate = LayoutInflater.from(this).inflate(R.layout.item_personal_flow_labels, null); TextView label = (TextView) inflate.findViewById(R.id.tv_label_name); label.setText(list.get(i)); mFlowLayout.addView(inflate); } }

運行一下:

Android FlowLayout流式布局實現詳解

咦!!!這時候發現我們添加的子View竟然沒添加進去?這是為什么呢?

這時候就不得不說一下onMeasure方法了,我們重寫一下onMeasure然后在看一下:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int totalWidth = MeasureSpec.getSize(widthMeasureSpec); int totalHeight = MeasureSpec.getSize(heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft(); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight); // 測量child child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec)); }

在運行一下:

Android FlowLayout流式布局實現詳解

我們可以看到確實是有View顯示出來了,可是為什么只有一個呢?

其實這里顯示的不是只有一個,而是所有的子View都蓋在一起了,所以看起來就像只有一個View,這是因為我們的onLayout里面getChildAt(i).layout(l,t,r,b);所有的子View擺放的位置都是一樣的,所以這邊要注意一下,自定義ViewGroup的時候一般onLayout和onMeasure都必須重寫,因為這兩個方法一個是計算子View的大小,一個是計算子View擺放的位置,缺少一個子View都會顯示不出來.

接下來我們在改寫一下onLayout方法讓子View都顯示出來

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).layout(l,t,r,b); l+=getChildAt(i).getMeasuredWidth(); } }

這樣子View就不會重疊在一起了,可是又發現一個問題,就是子View都在排在同一行了,我們怎么才能讓子View計算排滿一行就自動換行呢?

接下來我們定義一個行的類Line來保存一行的子View:

class Line{ int mWidth = 0;// 該行中所有的子View累加的寬度 int mHeight = 0;// 該行中所有的子View中高度的那個子View的高度 List<View> views = new ArrayList<View>(); public void addView(View view) {// 往該行中添加一個 views.add(view); mWidth += view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); mHeight = mHeight < childHeight ? childHeight : mHeight;//高度等于一行中最高的View } //擺放行中子View的位置 public void Layout(int l, int t){ } }

這樣我們就可以讓FlowLayout專門對Line進行擺放,然后Line專門對本行的View進行擺放

接下來針對Line我們重新寫一下onMeasure和onLayout方法

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int totalWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft(); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); restoreLine();// 還原數據,以便重新記錄 int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { break; } int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight); // 測量child child.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (mLine == null) { mLine = new Line(); } int measuredWidth = child.getMeasuredWidth(); mUsedWidth += measuredWidth;// 增加使用的寬度 if (mUsedWidth < sizeWidth) { //當本行的使用寬度小于行總寬度的時候直接加進line里面 mLine.addView(child); mUsedWidth += mHorizontalSpacing;// 加上間隔 if (mUsedWidth >= sizeWidth){ if (!newLine()){ break; } } }else {// 使用寬度大于總寬度。需要換行 if (mLine.getViewCount() == 0){//如果這行一個View也沒有超過也得加進去,保證一行最少有一個View mLine.addView(child); if (!newLine()) {// 換行 break; } }else { if (!newLine()) {// 換行 break; } mLine.addView(child); mUsedWidth += measuredWidth + mHorizontalSpacing; } } } if (mLine !=null && mLine.getViewCount() > 0 && !mLines.contains(mLine)){ mLines.add(mLine); } int totalHeight = 0; final int linesCount = mLines.size(); for (int i = 0; i < linesCount; i++) {// 加上所有行的高度 totalHeight += mLines.get(i).mHeight; } totalHeight += mVerticalSpacing * (linesCount - 1);// 加上所有間隔的高度 totalHeight += getPaddingTop() + getPaddingBottom();// 加上padding // 設置布局的寬高,寬度直接采用父view傳遞過來的最大寬度,而不用考慮子view是否填滿寬度,因為該布局的特性就是填滿一行后,再換行 // 高度根據設置的模式來決定采用所有子View的高度之和還是采用父view傳遞過來的高度 setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec)); }

可能有點長,不過注釋都寫得比較清楚了,簡單的說就是遍歷計算子View的寬高,動態加入行中,如果View的寬大于剩余的行寬就在取一行放下,接下來我們在重寫一些onLayout:

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mNeedLayout && changed){ mNeedLayout = false; int left = getPaddingLeft();//獲取最初的左上點 int top = getPaddingTop(); int count = mLines.size(); for (int i = 0; i < count; i++) { Line line = mLines.get(i); line.LayoutView(left,top);//擺放每一行中子View的位置 top +=line.mHeight+ mVerticalSpacing;//為下一行的top賦值 } } }

由于我們把子View的擺放都放在Line中了,所以onLayout比較簡單,接下來我們看一下Line的LayoutView方法:

public void LayoutView(int l, int t) { int left = l; int top = t; int count = getViewCount(); int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();//行的總寬度 //剩余的寬度,是除了View和間隙的剩余空間 int surplusWidth = layoutWidth - mWidth - mHorizontalSpacing * (count - 1); if (surplusWidth >= 0) { for (int i = 0; i < count; i++) { final View view = views.get(i); int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); //計算出每個View的頂點,是由最高的View和該View高度的差值除以2 int topOffset = (int) ((mHeight - childHeight) / 2.0 + 0.5); if (topOffset < 0) { topOffset = 0; } view.layout(left,top+topOffset,left+childWidth,top + topOffset + childHeight); left += childWidth + mVerticalSpacing;//為下一個View的left賦值 } } }

也是比較簡單,其實就是根據寬度動態計算而已,我們看看效果吧

Android FlowLayout流式布局實現詳解

可以了吧,看起來是大功告成了,可是我們發現左邊和右邊的間距好像不相等,能不能讓子View居中顯示呢?答案當然是可以的,接下來我們提供個方法,讓外部可以設置里面子View的對齊方式:

public interface AlienState { int RIGHT = 0; int LEFT = 1; int CENTER = 2; @IntDef(value = {RIGHT, LEFT, CENTER}) @interface Val {} } public void setAlignByCenter(@AlienState.Val int isAlignByCenter) { this.isAlignByCenter = isAlignByCenter; requestLayoutInner(); } private void requestLayoutInner() { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { requestLayout(); } }); }

提供一個setAlignByCenter的方法,分別有左對齊右對齊和居中對齊,然后我們在Line的layoutView中改寫一下:

//布局View if (i == 0) { switch (isAlignByCenter) { case AlienState.CENTER: left += surplusWidth / 2; break; case AlienState.RIGHT: left += surplusWidth; break; default: left = 0; break; }}

在layoutView中把剩余的寬度按照對齊的類型平分一下就得到我們要的效果了

Android FlowLayout流式布局實現詳解

好了,這樣就達到我們要的效果了.可是在回頭來看一下我們MainActivity里面的寫法會不會感覺很撮呢?對于習慣了ListView,RecyclerView的Adapter寫法的我們有沒有辦法改一下,像寫adapter一樣來寫布局呢?聰明的程序猿是沒有什么辦不到的,接下來我們就來改寫一下:

public void setAdapter(List<?> list, int res, ItemView mItemView) { if (list == null) { return; } removeAllViews(); int layoutPadding = dipToPx(getContext(), 8); setHorizontalSpacing(layoutPadding); setVerticalSpacing(layoutPadding); int size = list.size(); for (int i = 0; i < size; i++) { Object item = list.get(i); View inflate = LayoutInflater.from(getContext()).inflate(res, null); mItemView.getCover(item, new ViewHolder(inflate), inflate, i); addView(inflate); } } public abstract static class ItemView<T> { abstract void getCover(T item, ViewHolder holder, View inflate, int position); } class ViewHolder { View mConvertView; public ViewHolder(View mConvertView) { this.mConvertView = mConvertView; mViews = new SparseArray<>(); } public <T extends View> T getView(int viewId) { View view = mViews.get(viewId); if (view == null) { view = mConvertView.findViewById(viewId); mViews.put(viewId, view); } try { return (T) view; } catch (ClassCastException e) { e.printStackTrace(); } return null; } public void setText(int viewId, String text) { TextView view = getView(viewId); view.setText(text); } }

然后我們在MainActivity中在使用一下:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FlowLayout mFlowLayout = (FlowLayout) findViewById(R.id.mFlowLayout); List<String> list = new ArrayList<>(); list.add('java'); list.add('javaEE'); list.add('javaME'); list.add('c'); list.add('php'); list.add('ios'); list.add('c++'); list.add('c#'); list.add('Android'); mFlowLayout.setAlignByCenter(FlowLayout.AlienState.CENTER); mFlowLayout.setAdapter(list, R.layout.item, new FlowLayout.ItemView<String>() { @Override void getCover(String item, FlowLayout.ViewHolder holder, View inflate, int position) { holder.setText(R.id.tv_label_name,item); } }); }

怎么樣,是不是就根絕在跟使用adapter一樣了呢.

Demo已放到github,歡迎大家指點

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持好吧啦網。

標簽: Android
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产精品久久久一区二区| 国产精品欧美一区二区三区不卡| 久久av免费看| 国产亚洲一区| 国产精品精品国产一区二区| 日韩成人精品一区| 久久视频精品| 99亚洲视频| 巨乳诱惑日韩免费av| 国产图片一区| 一区二区亚洲视频| 国产美女亚洲精品7777| 日本成人在线视频网站| 久久久久久婷| 日韩精品诱惑一区?区三区| 在线视频亚洲| 国产精品视区| 亚洲日产av中文字幕| 欧美在线观看天堂一区二区三区| 国产日韩欧美一区| 国产成人免费视频网站视频社区| 国产91久久精品一区二区| 久久理论电影| 免费看欧美美女黄的网站| 久久精品国产久精国产| 亚洲欧美综合| 国产免费播放一区二区| 国产精品久久久久久久久免费高清 | 91精品蜜臀一区二区三区在线 | 国产亚洲亚洲| 日韩免费精品| 国产一区二区三区不卡视频网站| 久久久噜噜噜| 石原莉奈在线亚洲三区| 国产精品最新| 久久人人99| 18国产精品| 性欧美长视频| 日韩午夜视频在线| 国产美女高潮在线| 麻豆成人在线| 精品一区二区三区亚洲 | 午夜在线一区| 久久不卡国产精品一区二区| 亚洲v在线看| 亚洲色图国产| 狠狠久久伊人| 蜜桃av一区二区在线观看| 另类欧美日韩国产在线| 日韩成人精品一区二区三区| 欧美激情福利| 日韩一区二区久久| 麻豆国产一区| 亚洲免费高清| 蜜桃成人av| 久久精品99国产精品| 亚洲v在线看| 国产精品亚洲综合色区韩国| 欧美午夜精彩| 国产精品igao视频网网址不卡日韩| 精品欧美一区二区三区在线观看| 久久在线91| 伊人成人网在线看| 久久精品一区| 亚洲色图国产| 久久蜜桃资源一区二区老牛| 欧美有码在线| 婷婷中文字幕一区| 精品久久在线| 日本麻豆一区二区三区视频| 欧美综合另类| 精品国产三区在线| 人人爱人人干婷婷丁香亚洲| 欧美精品一区二区三区精品| 免费在线亚洲欧美| 亚洲精品大片| 水野朝阳av一区二区三区| 国产精品一国产精品k频道56| 黄色亚洲大片免费在线观看| 精品久久亚洲| 日本少妇精品亚洲第一区| 午夜国产精品视频| 日韩激情一区| 精品国产黄a∨片高清在线| 香蕉久久一区| 精品91久久久久| 91av亚洲| 久久精品国产99国产精品| 自拍自偷一区二区三区| 伊人久久婷婷| 久久九九电影| 精品国产成人| 美女性感视频久久| 欧美日本不卡高清| 午夜在线观看免费一区| 亚洲午夜av| 欧美aa在线观看| 国产一区二区精品久| 国产精品亲子伦av一区二区三区| 日本中文字幕一区二区视频 | 欧美日韩日本国产亚洲在线 | 伊人久久av| 欧美国产日韩电影| 日韩黄色av| 午夜精品影视国产一区在线麻豆| 亚洲综合三区| 欧美精品一二| 不卡一区综合视频| 亲子伦视频一区二区三区| 丁香婷婷久久| 精品中文在线| 精品三级av在线导航| 久久99国产精品视频| 久久99蜜桃| 久久伊人久久| 国模精品一区| 国产videos久久| 精品国产乱码久久久久久1区2匹| 欧美激情亚洲| 91亚洲国产成人久久精品| 日本精品黄色| 亚洲1234区| 国精品一区二区三区| 亚洲精品午夜av福利久久蜜桃| 婷婷色综合网| 伊人久久亚洲| 国产欧美日韩精品一区二区免费| 国产乱子精品一区二区在线观看| 国产精品伦一区二区| 精品亚洲成人| 欧美三区四区| 国产99久久久国产精品成人免费| 久久蜜桃精品| 男人天堂欧美日韩| 亚洲欧洲美洲国产香蕉| 日韩高清电影一区| 国产精品mm| 日韩精品永久网址| 136国产福利精品导航网址| 免费不卡中文字幕在线| 国产亚洲激情| 青青草伊人久久| 美女视频一区在线观看| 中文在线免费视频| 国精品一区二区三区| 视频一区在线视频| 天海翼亚洲一区二区三区| 日本特黄久久久高潮| 国产精品对白久久久久粗| 超级白嫩亚洲国产第一| 一区二区三区视频免费观看| 免费在线观看成人| 国产日本精品| 高清av一区| 91成人精品视频| 日本aⅴ精品一区二区三区| 精品网站999| 欧美性感美女一区二区| 涩涩涩久久久成人精品| 久久免费精品| 精品日韩毛片| 日韩精品一区二区三区免费视频| 国产精品v日韩精品v欧美精品网站| 成人一二三区| 国产精品美女久久久浪潮软件| 久久国产日韩欧美精品| 日韩电影免费网站| 丝瓜av网站精品一区二区| 欧美激情网址| 99成人在线| 国产精品777777在线播放 | 久草精品视频| 狠狠干成人综合网| 久久激情av| 天堂日韩电影| 日本aⅴ精品一区二区三区| 在线观看精品| 91精品一区| 国产真实久久| 国产毛片精品| 午夜精品网站| 麻豆极品一区二区三区| 国产美女精品| 国产在线日韩精品| 亚洲综合专区| 三上悠亚国产精品一区二区三区| 免费高清在线一区| 91中文字幕精品永久在线| 蜜桃视频免费观看一区| 黄色在线观看www| 亚洲欧美日本国产| а√在线中文在线新版| 日韩精品三区四区| 99久久亚洲精品| 国产精品一区二区av日韩在线| 欧美1级日本1级| 卡一卡二国产精品| 综合亚洲视频| 亚洲福利国产| 久久精品国产亚洲一区二区三区|