Android LinearLayout實(shí)現(xiàn)自動(dòng)換行效果
在我們開發(fā)過程中會(huì)經(jīng)常遇見一些客戶要求但是Android系統(tǒng)又不提供的效果,這時(shí)我們只能自己動(dòng)手去實(shí)現(xiàn)它,或者從網(wǎng)絡(luò)上借鑒他人的資源,本著用別人不如自己會(huì)做的心態(tài),在此我總結(jié)了一下Android中如何實(shí)現(xiàn)自動(dòng)換行的LinearLayout。
在本文中,說是LinearLayout其實(shí)是繼承自GroupView,在這里主要重寫了兩個(gè)方法,onMeasure、onLayout方法,下面我對(duì)此加以介紹。(代碼中使用了AttributeSet,由于時(shí)間問題不再予以介紹)。
1. onMeasure是干什么的?
在ViewGroup的創(chuàng)建過程中,onMeasure是在onLayout之前的,所以在此先對(duì)onMeasure進(jìn)行介紹,onMeasure方法是計(jì)算子控件與父控件在屏幕中所占長(zhǎng)寬大小的,onMeasure傳入兩個(gè)參數(shù)——widthMeasureSpec和heightMeasureSpec. 這兩個(gè)參數(shù)指明控件可獲得的空間以及關(guān)于這個(gè)空間描述的元數(shù)據(jù).
int withMode = MeasureSpec.getMode(widthMeasureSpec); int withSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Mode有3種模式分別是UNSPECIFIED, EXACTLY和AT_MOST,如果是AT_MOST,Size代表的是最大可獲得的空間;如果是EXACTLY,Size代表的是精確的尺寸;如果是UNSPECIFIED,就是你想要多少就有多少。經(jīng)過代碼測(cè)試就知道,當(dāng)我們?cè)O(shè)置width或height為fill_parent時(shí),容器在布局時(shí)調(diào)用子 view的measure方法傳入的模式是EXACTLY,因?yàn)樽觱iew會(huì)占據(jù)剩余容器的空間,所以它大小是確定的。而當(dāng)設(shè)置為 wrap_content時(shí),容器傳進(jìn)去的是AT_MOST, 表示子view的大小最多是多少,這樣子view會(huì)根據(jù)這個(gè)上限來設(shè)置自己的尺寸。當(dāng)子view的大小設(shè)置為精確值時(shí),容器傳入的是EXACTLY。
2. onLayout是干什么的?
與onMesaure相比,onLayout更加容易理解,它的作用就是調(diào)座位,就是把所有的子View根據(jù)不同的需要,通過View. layout(int l, int t, int r, int b)方法指定它所在的位置。
3. 解決問題
只要對(duì)onMeasure和onLayout加以理解,對(duì)于該篇所要實(shí)現(xiàn)的功能就不再難以實(shí)現(xiàn),下面貼上代碼,并在代碼中講解。
WaroLinearLayout.java
public class WarpLinearLayout extends ViewGroup { private Type mType; private List<WarpLine> mWarpLineGroup; public WarpLinearLayout(Context context) { this(context, null); } public WarpLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, R.style.WarpLinearLayoutDefault); } public WarpLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mType = new Type(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int withMode = MeasureSpec.getMode(widthMeasureSpec); int withSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int with = 0; int height = 0; int childCount = getChildCount(); /** * 在調(diào)用childView。getMeasre之前必須先調(diào)用該行代碼,用于對(duì)子View大小的測(cè)量 */ measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 計(jì)算寬度 */ switch (withMode) { case MeasureSpec.EXACTLY: with = withSize; break; case MeasureSpec.AT_MOST: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); with = with > withSize ? withSize : with; break; case MeasureSpec.UNSPECIFIED: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); break; default: with = withSize; break; } /** * 根據(jù)計(jì)算出的寬度,計(jì)算出所需要的行數(shù) */ WarpLine warpLine = new WarpLine(); /** * 不能夠在定義屬性時(shí)初始化,因?yàn)閛nMeasure方法會(huì)多次調(diào)用 */ mWarpLineGroup = new ArrayList<WarpLine>(); for (int i = 0; i < childCount; i++) { if (warpLine.lineWidth + getChildAt(i).getMeasuredWidth() + mType.horizontal_Space > with) { if (warpLine.lineView.size() == 0) { warpLine.addView(getChildAt(i)); mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); } else { mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); warpLine.addView(getChildAt(i)); } } else { warpLine.addView(getChildAt(i)); } } /** * 添加最后一行 */ if (warpLine.lineView.size() > 0 && !mWarpLineGroup.contains(warpLine)) { mWarpLineGroup.add(warpLine); } /** * 計(jì)算寬度 */ height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mWarpLineGroup.size(); i++) { if (i != 0) { height += mType.vertical_Space; } height += mWarpLineGroup.get(i).height; } switch (heightMode) { case MeasureSpec.EXACTLY: height = heightSize; break; case MeasureSpec.AT_MOST: height = height > heightSize ? heightSize : height; break; case MeasureSpec.UNSPECIFIED: break; default: break; } setMeasuredDimension(with, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { t = getPaddingTop(); for (int i = 0; i < mWarpLineGroup.size(); i++) { int left = getPaddingLeft(); WarpLine warpLine = mWarpLineGroup.get(i); int lastWidth = getMeasuredWidth() - warpLine.lineWidth; for (int j = 0; j < warpLine.lineView.size(); j++) { View view = warpLine.lineView.get(j); if (isFull()) {//需要充滿當(dāng)前行時(shí) view.layout(left, t, left + view.getMeasuredWidth() + lastWidth / warpLine.lineView.size(), t + view.getMeasuredHeight()); left += view.getMeasuredWidth() + mType.horizontal_Space + lastWidth / warpLine.lineView.size(); } else { switch (getGrivate()) { case 0://右對(duì)齊 view.layout(left + lastWidth, t, left + lastWidth + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; case 2://居中對(duì)齊 view.layout(left + lastWidth / 2, t, left + lastWidth / 2 + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; default://左對(duì)齊 view.layout(left, t, left + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; } left += view.getMeasuredWidth() + mType.horizontal_Space; } } t += warpLine.height + mType.vertical_Space; } } /** * 用于存放一行子View */ private final class WarpLine { private List<View> lineView = new ArrayList<View>(); /** * 當(dāng)前行中所需要占用的寬度 */ private int lineWidth = getPaddingLeft() + getPaddingRight(); /** * 該行View中所需要占用的最大高度 */ private int height = 0; private void addView(View view) { if (lineView.size() != 0) { lineWidth += mType.horizontal_Space; } height = height > view.getMeasuredHeight() ? height : view.getMeasuredHeight(); lineWidth += view.getMeasuredWidth(); lineView.add(view); } } /** * 對(duì)樣式的初始化 */ private final static class Type { /* *對(duì)齊方式 right 0,left 1,center 2 */ private int grivate; /** * 水平間距,單位px */ private float horizontal_Space; /** * 垂直間距,單位px */ private float vertical_Space; /** * 是否自動(dòng)填滿 */ private boolean isFull; Type(Context context, AttributeSet attrs) { if (attrs == null) { return; } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WarpLinearLayout); grivate = typedArray.getInt(R.styleable.WarpLinearLayout_grivate, grivate); horizontal_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_horizontal_Space, horizontal_Space); vertical_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_vertical_Space, vertical_Space); isFull = typedArray.getBoolean(R.styleable.WarpLinearLayout_isFull, isFull); } } public int getGrivate() { return mType.grivate; } public float getHorizontal_Space() { return mType.horizontal_Space; } public float getVertical_Space() { return mType.vertical_Space; } public boolean isFull() { return mType.isFull; } public void setGrivate(int grivate) { mType.grivate = grivate; } public void setHorizontal_Space(float horizontal_Space) { mType.horizontal_Space = horizontal_Space; } public void setVertical_Space(float vertical_Space) { mType.vertical_Space = vertical_Space; } public void setIsFull(boolean isFull) { mType.isFull = isFull; } /** * 每行子View的對(duì)齊方式 */ public final static class Gravite { public final static int RIGHT = 0; public final static int LEFT = 1; public final static int CENTER = 2; } }
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="WarpLinearLayout"> <attr name="grivate" format="enum"><!--對(duì)齊方式 !--> <enum name="right" value="0"></enum> <enum name="left" value="1"></enum> <enum name="center" value="2"></enum> </attr> <attr name="horizontal_Space" format="dimension"></attr> <attr name="vertical_Space" format="dimension"></attr> <attr name="isFull" format="boolean"></attr> </declare-styleable> </resources>
WarpLinearLayoutDefault
<style name="WarpLinearLayoutDefault"> <item name="grivate">left</item> <item name="horizontal_Space">20dp</item> <item name="vertical_Space">20dp</item> <item name="isFull">false</item> </style>
MainActivity.java
public class MainActivity extends Activity { private Button btn; private WarpLinearLayout warpLinearLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); warpLinearLayout = (WarpLinearLayout) findViewById(R.id.warpLinearLayout); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int n = new Random().nextInt(10) + 5; StringBuffer stringBuffer = new StringBuffer(); Random random = new Random(); Log.i("WarpLinearLayout","n="+n); for (int i = 0; i < n; i++) { stringBuffer.append((char)(65+random.nextInt(26))); Log.i("WarpLinearLayout", "StringBuffer=" + stringBuffer.toString()); } TextView tv = new TextView(MainActivity.this); tv.setText(stringBuffer.toString()+"000"); tv.setBackgroundResource(R.drawable.radius_backgroup_yellow); tv.setPadding(10,10,10,10); warpLinearLayout.addView(tv); } }); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="add" android:textSize="20dp" /> <com.example.customview.viewgroup.WarpLinearLayout android:id="@+id/warpLinearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn" android:background="#FF00FF00" android:padding="10dp" app:grivate="right" app:horizontal_Space="10dp" app:isFull="false" app:vertical_Space="10dp"></com.example.customview.viewgroup.WarpLinearLayout> </RelativeLayout>
運(yùn)行效果圖如下:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
安卓圖片反復(fù)壓縮后為什么普遍會(huì)變綠而不是其它顏色?
今天小編就為大家分享一篇關(guān)于安卓圖片反復(fù)壓縮后為什么普遍會(huì)變綠而不是其它顏色?,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Android自定義textview實(shí)現(xiàn)豎直滾動(dòng)跑馬燈效果
這篇文章主要為大家詳細(xì)介紹了Android自定義textview實(shí)現(xiàn)豎直滾動(dòng)跑馬燈效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Jetpack Compose實(shí)現(xiàn)動(dòng)畫效果的方法詳解
compose為支持動(dòng)畫提供了大量的 api,通過這些 api 我們可以輕松實(shí)現(xiàn)動(dòng)畫效果。本文將為大家介紹利用compose實(shí)現(xiàn)的多種動(dòng)畫效果的示例代碼,需要的可以參考一下2022-02-02Android異常 java.lang.IllegalStateException解決方法
這篇文章主要介紹了Android異常 java.lang.IllegalStateException解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android 中TextView中跑馬燈效果的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 中TextView中跑馬燈效果的實(shí)現(xiàn)方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02Android使用DrawerLayout實(shí)現(xiàn)雙向側(cè)滑菜單
這篇文章主要為大家詳細(xì)介紹了Android使用DrawerLayout實(shí)現(xiàn)雙向側(cè)滑菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android可篩選的彈窗控件CustomFiltControl
這篇文章主要為大家詳細(xì)介紹了Android可篩選的彈窗控件CustomFiltControl,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07