国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

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

Android FlowLayout流式布局實現詳解

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

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

最近使用APP的時候經常看到有

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
相關文章:
主站蜘蛛池模板: 欧美亚洲日本韩国一级毛片 | 精品一区二区三区免费观看 | 免费人成在线观看网站品爱网 | 丁香婷婷影音先锋5566 | 91热国产| 日本久久综合网 | 在线免费观看成年人视频 | 狠狠色综合网站久久久久久久 | 色伊人国产高清在线 | 秘书高跟黑色丝袜国产91在线 | 91精品国产美女福到在线不卡 | 宅男66lu国产乱在线观看 | 欧美日韩久久 | 欧美一级特黄视频 | 国产精品日韩欧美一区二区 | 草草影院欧美三级日本 | 爽爽爽爽爽爽a成人免费视频 | 欧美一级片在线观看 | 精品免费在线视频 | 天堂色网站 | 日韩一区二区在线观看 | 国产精品网站 夜色 | 黄色美女视频网站 | 亚欧国产| 国内精品久久久久久久影视麻豆 | 欧美日本亚洲国产一区二区 | 美国欧美一级毛片 | 国产精品三级国语在线看 | 欧美性色生活免费观看 | 欧美精品久久久久久久免费观看 | 久久精品视频一区二区三区 | 欧美一级毛级毛片 | 久久99精品热在线观看15 | 一区二区高清在线 | 国产91网址| 亚洲精品免费视频 | 日本色哟哟| 国产精品不卡 | 欧美日韩国产一区二区三区播放 | 欧美一级毛片免费大全 | 亚洲午夜精品一级在线 |