色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Android FlowLayout流式布局實(shí)現(xiàn)詳解

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

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

最近使用APP的時(shí)候經(jīng)常看到有

Android FlowLayout流式布局實(shí)現(xiàn)詳解

這種流式布局 ,今天我就跟大家一起來(lái)動(dòng)手?jǐn)]一個(gè)這種自定義控件.

首先說(shuō)一下自定義控件的流程:

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

View的自定義流程:

繼承一個(gè)View-->重寫(xiě)onMeasure方法-->重寫(xiě)onDraw方法-->定義自定義屬性-->處理手勢(shì)操作

ViewGroup的自定義流程:

繼承一個(gè)ViewGroup-->重寫(xiě)onMeasure方法-->重寫(xiě)onLayout-->重寫(xiě)onDraw方法->定義自定義屬性-->處理手勢(shì)操作

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

onMeasure:用來(lái)計(jì)算,計(jì)算自身顯示在頁(yè)面上的大小

onLayout:用來(lái)計(jì)算子View擺放的位置,因?yàn)閂iew已經(jīng)是最小單元了,所以沒(méi)有字View,所以沒(méi)有onLayout方法

onDraw:用來(lái)繪制你想展示的東西

定義自定義屬性就是暴露一些屬性給外部調(diào)用

好了,了解了自定義View的基本自定義流程,我們可以知道我們應(yīng)該需要自定義一個(gè)ViewGroup就可以滿(mǎn)足該需求.

首先自定義一個(gè)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是必須重寫(xiě)的,不然系統(tǒng)不知道你這個(gè)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中設(shè)置數(shù)據(jù)

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); } }

運(yùn)行一下:

Android FlowLayout流式布局實(shí)現(xiàn)詳解

咦!!!這時(shí)候發(fā)現(xiàn)我們添加的子View竟然沒(méi)添加進(jìn)去?這是為什么呢?

這時(shí)候就不得不說(shuō)一下onMeasure方法了,我們重寫(xiě)一下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); // 測(cè)量child child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec)); }

在運(yùn)行一下:

Android FlowLayout流式布局實(shí)現(xiàn)詳解

我們可以看到確實(shí)是有View顯示出來(lái)了,可是為什么只有一個(gè)呢?

其實(shí)這里顯示的不是只有一個(gè),而是所有的子View都蓋在一起了,所以看起來(lái)就像只有一個(gè)View,這是因?yàn)槲覀兊膐nLayout里面getChildAt(i).layout(l,t,r,b);所有的子View擺放的位置都是一樣的,所以這邊要注意一下,自定義ViewGroup的時(shí)候一般onLayout和onMeasure都必須重寫(xiě),因?yàn)檫@兩個(gè)方法一個(gè)是計(jì)算子View的大小,一個(gè)是計(jì)算子View擺放的位置,缺少一個(gè)子View都會(huì)顯示不出來(lái).

接下來(lái)我們?cè)诟膶?xiě)一下onLayout方法讓子View都顯示出來(lái)

@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就不會(huì)重疊在一起了,可是又發(fā)現(xiàn)一個(gè)問(wèn)題,就是子View都在排在同一行了,我們?cè)趺床拍茏屪覸iew計(jì)算排滿(mǎn)一行就自動(dòng)換行呢?

接下來(lái)我們定義一個(gè)行的類(lèi)Line來(lái)保存一行的子View:

class Line{ int mWidth = 0;// 該行中所有的子View累加的寬度 int mHeight = 0;// 該行中所有的子View中高度的那個(gè)子View的高度 List<View> views = new ArrayList<View>(); public void addView(View view) {// 往該行中添加一個(gè) 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專(zhuān)門(mén)對(duì)Line進(jìn)行擺放,然后Line專(zhuān)門(mén)對(duì)本行的View進(jìn)行擺放

接下來(lái)針對(duì)Line我們重新寫(xiě)一下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();// 還原數(shù)據(jù),以便重新記錄 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); // 測(cè)量child child.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (mLine == null) { mLine = new Line(); } int measuredWidth = child.getMeasuredWidth(); mUsedWidth += measuredWidth;// 增加使用的寬度 if (mUsedWidth < sizeWidth) { //當(dāng)本行的使用寬度小于行總寬度的時(shí)候直接加進(jìn)line里面 mLine.addView(child); mUsedWidth += mHorizontalSpacing;// 加上間隔 if (mUsedWidth >= sizeWidth){ if (!newLine()){ break; } } }else {// 使用寬度大于總寬度。需要換行 if (mLine.getViewCount() == 0){//如果這行一個(gè)View也沒(méi)有超過(guò)也得加進(jìn)去,保證一行最少有一個(gè)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 // 設(shè)置布局的寬高,寬度直接采用父view傳遞過(guò)來(lái)的最大寬度,而不用考慮子view是否填滿(mǎn)寬度,因?yàn)樵摬季值奶匦跃褪翘顫M(mǎn)一行后,再換行 // 高度根據(jù)設(shè)置的模式來(lái)決定采用所有子View的高度之和還是采用父view傳遞過(guò)來(lái)的高度 setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec)); }

可能有點(diǎn)長(zhǎng),不過(guò)注釋都寫(xiě)得比較清楚了,簡(jiǎn)單的說(shuō)就是遍歷計(jì)算子View的寬高,動(dòng)態(tài)加入行中,如果View的寬大于剩余的行寬就在取一行放下,接下來(lái)我們?cè)谥貙?xiě)一些onLayout:

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mNeedLayout && changed){ mNeedLayout = false; int left = getPaddingLeft();//獲取最初的左上點(diǎn) 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比較簡(jiǎn)單,接下來(lái)我們看一下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(); //計(jì)算出每個(gè)View的頂點(diǎn),是由最高的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;//為下一個(gè)View的left賦值 } } }

也是比較簡(jiǎn)單,其實(shí)就是根據(jù)寬度動(dòng)態(tài)計(jì)算而已,我們看看效果吧

Android FlowLayout流式布局實(shí)現(xiàn)詳解

可以了吧,看起來(lái)是大功告成了,可是我們發(fā)現(xiàn)左邊和右邊的間距好像不相等,能不能讓子View居中顯示呢?答案當(dāng)然是可以的,接下來(lái)我們提供個(gè)方法,讓外部可以設(shè)置里面子View的對(duì)齊方式:

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(); } }); }

提供一個(gè)setAlignByCenter的方法,分別有左對(duì)齊右對(duì)齊和居中對(duì)齊,然后我們?cè)贚ine的layoutView中改寫(xiě)一下:

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

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

Android FlowLayout流式布局實(shí)現(xiàn)詳解

好了,這樣就達(dá)到我們要的效果了.可是在回頭來(lái)看一下我們MainActivity里面的寫(xiě)法會(huì)不會(huì)感覺(jué)很撮呢?對(duì)于習(xí)慣了ListView,RecyclerView的Adapter寫(xiě)法的我們有沒(méi)有辦法改一下,像寫(xiě)adapter一樣來(lái)寫(xiě)布局呢?聰明的程序猿是沒(méi)有什么辦不到的,接下來(lái)我們就來(lái)改寫(xiě)一下:

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); } }

然后我們?cè)贛ainActivity中在使用一下:

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,歡迎大家指點(diǎn)

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 91亚洲人成手机在线观看 | 日本一区视频在线观看 | 久久精品女人毛片国产 | 免费看真人a一级毛片 | 欧美成人26uuu欧美毛片 | 欧美成人精品在线 | 亚洲国内精品 | 亚洲精品在线看 | 一个人看的免费观看日本视频www | 欧美日韩精品国产一区二区 | 久久久香蕉 | 中文字幕第9页 | 成人在线中文字幕 | 日韩视频久久 | 国产精品久久久久久福利漫画 | 国产成人综合网在线播放 | 成人国产精品免费网站 | 日韩精品在线观看免费 | 自拍1页| 三上悠亚免费一区二区在线 | 亚洲一区在线视频 | 九九九九在线精品免费视频 | 国产欧美日韩一区 | 免费特黄一区二区三区视频一 | 2级毛片| 美国第一毛片 | 国产欧美日韩在线观看精品 | 国产一级在线观看 | 欧美成人全部费免网站 | 成年人网站在线观看视频 | 欧美一级毛级毛片 | 日本一区二区三区在线 视频 | 韩国毛片基地 | 在线精品亚洲 | 日韩精品另类天天更新影院 | 国产日韩三级 | 精品国产香蕉在线播出 | 国产一区二区三区在线免费观看 | 久草在线在线观看 | 亚洲一区欧美二区 | 色老头一区二区三区在线观看 |