1. 程式人生 > >安卓流式佈局(可換行的標籤)

安卓流式佈局(可換行的標籤)

最近,公司的專案中需要展示商品的規格和屬性,但是不同的商品屬性個數也是不一樣的,

怎麼能夠讓超過一行的屬性自動換行呢?這就需要用到我們的流式佈局,下面先看看效果圖


這篇文章更改的,流式是怎麼實現的還是請先看完上邊這篇文章.

在將樓主的原始碼下載下來使用的時候遇到以下幾個問題,本文將圍繞這幾個小問題進行講解

  首先樓主的這個自定義控制元件始終預設鋪滿螢幕的,但是感覺很奇怪,因為在onMeasure這個方法中

          已經根據控制元件設定的模式測量模式進行過計算了,按道理說不應該是鋪滿螢幕的!

         而且我設定的高度是wrap_content(自適應)   打了斷點試的時候  發現測量出來的距離並不是鋪滿螢幕,

         而是真是高度  又認認真真的看了一下測量發方法  發現樓主將super.onMeasure(widthMeasureSpec, heightMeasureSpec);放在了

         方法結束的位置大哭  是不是很扯   這句話放在最後的話,就相當於我測量了半天,測量到最後不使用這個

        測量值,而使用其父類(ViewGroup)的測量結果,也就是預設結果 

    解決方案:把這句話移到方法首句或者直接刪除這句話

接著  確實是自適應了  但是隻是單純的換行不行   我們需要點選之後知道我們選中了那個   並且選中的這個背景顏色需要變

    簡單地分析下,我們需要做以下幾件事:

        1.兩個自定義屬性  分別是選中和未選中的背景顏色

         2.獲取所有控制元件的的位置

         3.判斷點選的點是不是包含在某個子控制元件中

         4,如果是包含在某個子控制元件中,設定回撥

下面我們具體去完成我們這幾個步驟:

1.兩個自定義屬性

     先在values下建立一個attrs的xml檔案   分別代表選中和未選中的兩個狀態

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="flowlayout">
        <attr name="back_selected" format="reference" />
        <attr name="back_un_selected" format="reference" />
    </declare-styleable>
</resources>
      接著  建立兩個shape   分別代表選中和未選中時的背景顏色狀態

       選中狀態

<?xml version="1.0" encoding="utf-8"?>    
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <corners android:radius="1dp" />
    <stroke
        android:width="2dp"
        android:color="#ff6600" />
    <solid android:color="#ffffff" />
</shape>
      未選中狀態
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <corners android:radius="1dp" />
    <stroke
        android:width="2dp"
        android:color="#000000" />
    <solid android:color="#ffffff" />
</shape>
      接著在自定義控制元件的構造方法中獲取這兩個自定義屬性:
     public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		childLocationList = new HashMap<Integer, Rect>();
		// 獲取自定義屬性的值
		TypedArray typedArray = context.getTheme().obtainStyledAttributes(
				attrs, R.styleable.flowlayout, defStyle, 0);
		int  back_selected = typedArray.getResourceId(
				R.styleable.flowlayout_back_selected, 0);// 選中時的背景資源id
		int back_unselected = typedArray.getResourceId(
				R.styleable.flowlayout_back_un_selected, 0);// 未選中時的背景資源id
		typedArray.recycle();
	}
     最後在  佈局檔案中為這兩個屬性賦值  注意名稱空間
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:flayout="http://schemas.android.com/apk/res/com.czm.flowlayout"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
    <com.czm.flowlayout.FlowLayout
        android:id="@+id/flowlayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        flayout:back_selected="@drawable/shape_label_selected"
        flayout:back_un_selected="@drawable/shape_label_unselected" >
    </com.czm.flowlayout.FlowLayout>
</RelativeLayout>
2.獲得所有控制元件的位置

    獲得子控制元件的位置    應該在我們指定了子控制元件的位置之後再去獲取  也就是說在onLayout方法的最後

      我們新增一個方法  getAllChildViewLocation

     我們先分析一下思路 :樓主當時是這樣儲存所有的子控制元件的

  <span style="font-size:14px;"> // 儲存所有子View
	private List<List<View>> mAllChildViews = new ArrayList<List<View>>();</span>
     先將每一行的控制元件存在一個List集合中  再將所有的行List再存到List集合中

       這樣  我們就可以根據行數獲取到指定行中所有的控制元件  再獲取指定行指定第幾個的控制元件

         我們能拿到這個具體的控制元件  就能拿到控制元件所在的位置

        將拿到的位置存到一個鍵值對型別的集合中      鍵表示在這個流式佈局中第幾個   值是對應控制元件的位置  

        至於為什麼要存在一個鍵值對型別的集合中,等會再說

         接著怎麼去存  


     看了這個圖,應該知道鍵怎麼存了

      值是獲取了子控制元件所在的矩形,因為矩形有個方法contains(int x, int y)   可以判斷一個點是否包含在這個矩形中

       建立這個集合最好是在構造方法中建立

    private HashMap<Integer, Rect> childLocationList = new HashMap<Integer, Rect>();
     獲取並記錄子控制元件的位置記錄
        /**
	 * 獲取所有的子控制元件的位置並記錄
	 */
	private void getAllChildViewLocation() {
		int countBefore = 0;
		for (int i = 0; i < mAllChildViews.size(); i++) {
			if (i > 0) {
				countBefore += mAllChildViews.get(i - 1).size();
			}
			for (int j = 0; j < mAllChildViews.get(i).size(); j++) {
				View view = mAllChildViews.get(i).get(j);
				Rect rect = new Rect(view.getLeft(), view.getTop(),
						view.getRight(), view.getBottom());
				childLocationList.put(countBefore + j, rect);
			}
		}

	}
3.判斷點選的是哪個

      現在有控制元件的位置了  我們只要能知道點的位置就夠了  下面重寫onTouchEvent方法

       /**
	 * 這裡我們需要判斷子控制元件是否被點選 點選的是哪個子控制元件
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			downX = (int) event.getX();
			downY = (int) event.getY();
			downTime = SystemClock.currentThreadTimeMillis();
			break;
		case MotionEvent.ACTION_UP:
			long upTime = SystemClock.currentThreadTimeMillis();
			if (upTime - downTime <= 50) {// 如果手指按下和手指離開鍵盤的時間小於50毫秒有效
				for (int i = 0; i < childLocationList.size(); i++) {
					if (childLocationList.get(i).contains(downX, downY)) {// 如果子控制元件所在位置(矩形)包括這個點
						if (onLabelSelectedListener != null) {
							onLabelSelectedListener.onSelected(i);//這個i是什麼   就是子控制元件是第幾個 直接把這個傳過去   現在知<span style="white-space:pre">												</span>//道這個集合的鍵做什麼用了吧
						}
						// 將記錄的上一個控制元件顏色改成未選中狀態
						if (lastSelectedPosition != -1) {
							TextView lastSelectedView = (TextView) getChildAt(lastSelectedPosition);
							lastSelectedView
									.setBackgroundResource(back_unselected);
						}
						// 將當前選中的控制元件背景改成選中狀態 並記錄
						TextView childAt = (TextView) getChildAt(i);
						childAt.setBackgroundResource(back_selected);
						lastSelectedPosition = i;
						break;
					}
				}
			}
			break;

		default:
			break;
		}
		return true;
	}
   4.設定回撥和改變狀態
<span style="white-space:pre">	</span>/**
	 * 子控制元件(標籤)選中監聽
	 * 
	 * @author HaiPeng
	 * 
	 */
	public interface OnLabelSelectedListener {
		void onSelected(int position);
	}

	/**
	 * 設定子控制元件選中時的監聽
	 * 
	 * @param onLabelSelectedListener
	 */
	public void setOnLabelSelectedListener(
			OnLabelSelectedListener onLabelSelectedListener) {
		this.onLabelSelectedListener = onLabelSelectedListener;
	}

	/**
	 * 記錄上一次點選的是哪個子控制元件
	 */
	private int lastSelectedPosition = -1;
        private OnLabelSelectedListener onLabelSelectedListener;
對了在測量之前給子控制元件設定上沒有選中的背景
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		// 父控制元件傳進來的寬度和高度以及對應的測量模式
		int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
		int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
		int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
		int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

		// 如果當前ViewGroup的寬高為wrap_content的情況
		int width = 0;// 自己測量的 寬度
		int height = 0;// 自己測量的高度
		// 記錄每一行的寬度和高度
		int lineWidth = 0;
		int lineHeight = 0;

		// 獲取子view的個數
		int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			<span style="color:#FF6666;">child.setBackgroundResource(back_unselected);</span>
			// 測量子View的寬和高
			measureChild(child, widthMeasureSpec, heightMeasureSpec);
			// 得到LayoutParams
			MarginLayoutParams lp = (MarginLayoutParams) child
					.getLayoutParams();
			// 子View佔據的寬度
			int childWidth = child.getMeasuredWidth() + lp.leftMargin
					+ lp.rightMargin;
			// 子View佔據的高度
			int childHeight = child.getMeasuredHeight() + lp.topMargin
					+ lp.bottomMargin;
			// 換行時候
			if (lineWidth + childWidth > sizeWidth) {
				// 對比得到最大的寬度
				width = Math.max(width, lineWidth);
				// 重置lineWidth
				lineWidth = childWidth;
				// 記錄行高
				height += lineHeight;
				lineHeight = childHeight;
			} else {// 不換行情況
					// 疊加行寬
				lineWidth += childWidth;
				// 得到最大行高
				lineHeight = Math.max(lineHeight, childHeight);
			}
			// 處理最後一個子View的情況
			if (i == childCount - 1) {
				width = Math.max(width, lineWidth);
				height += lineHeight;
			}
		}
		// wrap_content
		setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth
				: width, modeHeight == MeasureSpec.EXACTLY ? sizeHeight
				: height);
	}
最後我們在MainActivity中呼叫一下
<span style="white-space:pre">	</span>private void initChildViews() {
		// TODO Auto-generated method stub
		mFlowLayout = (FlowLayout) findViewById(R.id.flowlayout);
		MarginLayoutParams lp = new MarginLayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		lp.leftMargin = 5;
		lp.rightMargin = 5;
		lp.topMargin = 5;
		lp.bottomMargin = 5;
		for (int i = 0; i < mNames.length; i++) {
			TextView view = new TextView(this);
			view.setText(mNames[i]);
			view.setTextColor(Color.BLACK);
			view.setPadding(5, 5, 5, 5);
			// view.setBackgroundDrawable(getResources().getDrawable(R.drawable.textview_bg));
			mFlowLayout.addView(view, lp);
		}
		mFlowLayout.setOnLabelSelectedListener(new OnLabelSelectedListener() {

			@Override
			public void onSelected(int position) {
				Toast.makeText(MainActivity.this, "第" + position + "個被點選了",
						Toast.LENGTH_SHORT).show();
			}
		});
	}
效果圖在最上邊,大家已經看過了

點選這裡下載原始碼

相關推薦

佈局(標籤)

最近,公司的專案中需要展示商品的規格和屬性,但是不同的商品屬性個數也是不一樣的, 怎麼能夠讓超過一行的屬性自動換行呢?這就需要用到我們的流式佈局,下面先看看效果圖 這篇文章更改的,流式是怎麼實現的還是請先看完上邊這篇文章. 在將樓主的原始碼下載下來使用的時候遇到以下幾

Android 實現FlowLayout佈局(類似熱門標籤

今天跟大家分享一下FlowLayout,最近專案中有遇到熱門標籤這個樣的佈局(文章末尾可下載原始碼),如下圖: 一,建立FlowLayout並繼承ViewGroup FlowLayout 類主要實現onMeasure,onLayout和generateL

Android 佈局FlowLayout 實現關鍵字標籤

1.介紹 流式佈局的應用還是很廣泛的,比如搜尋熱詞、關鍵詞標籤等,GitHub上已經有很多這樣的佈局了,但是還是想著自己實現一下,最近一直在學自定義控制元件,也鞏固一下所學的知識。 本文實現的效果如下圖所示: 2.思路 繼承自RelativeL

中使用佈局實現標籤

我們在開發的時候通常需要加標籤,對於這個標籤怎麼說呢,反正也挺複雜的,最初開發這個標籤的時候還是沒有思路的,後來在github上面查找了一下資料,瞭解了通過流式佈局來實現這個標籤,我記得開始的時候我寫標籤的時候是三個TextView一個一個新增進去的,後來感覺還是不太好,所

沉浸狀態列開發 輸入法彈出遮擋佈局問題解決

公司新版APP採用沉浸式狀態列開發,開發過程中遇到一些奇葩問題,其中一個問題就是輸入法彈出後,佈局沒有被頂上去,無法看到輸入內容 解決方案,在程式碼中,設定 getWindow().setSoftInputMode( WindowManager.LayoutPara

Android開發之玩轉FlexboxLayout佈局用於普通控制元件實現佈局,也結合RecycleView實現佈局

在這之前,我曾認真的研究過鴻洋大神的Android 自定義ViewGroup 實戰篇 -> 實現FlowLayout,按照大神的思路寫出了一個流式佈局,所有的東西都是難者不會會者不難,當自己能自定義流式佈局的時候就會覺得這東西原來很簡單了。如果各位小夥伴也看過那篇

100Android程式碼自定義一個佈局-FlowLayout

首先來看一下 手淘HD - 商品詳情 - 選擇商品屬性 頁面的UI 商品有很多尺碼,而且展現每個尺碼所需要的View的大小也不同(主要是寬度),所以在從伺服器端拉到資料之前,展現所有尺碼所需要的行數和每一行的個數都無法確定,因此不能直接使用GridView

手機上的python運環境-qpython

clu log bcs var arr lec yun upa ros %E7%AC%AC%E4%BA%8C%E5%B1%8APHP%E5%85%A8%E7%90%83%E5%BC%80%E5%8F%91%E8%80%85%E5%A4%A7%E4%BC%9A%E5%90%A

9】在windows運命令中操縱數據庫

order rom sqli select color ros stat pac tool 在windows運行命令中操縱數據庫 Microsoft Windows [版本 10.0.10586] (c) 2015 Microsoft Corporation。保留所

app開發-04- app運的運和調試

許可 完成 acc targe settings andro 安卓app開發 href 一個 app 運行的運行和調試 本篇介紹在 Android Studio 開發工具,運行調試設備:真機和虛擬機。 真機調試(USB 連接手機) 盡量使用真機進行調試,無論是調試效果和速度

自定義View,佈局

  寫的比較基礎, 備忘使用。 public class FlowLayout extends ViewGroup { public FlowLayout(Context context) { this(context, null); }

佈局簡單實現

//主方法 //建立集合 private List<String> mStrings=new ArrayList<>(); final EditText ed_search=(EditText)findViewById(R.id.ed_search

YYLabel 自動佈局 numberOfLines無效

最近是用Masonry自動佈局YYLabel的時候,發現設定了label.numberOfLines = 0,2,3;這些東西之後,label還是沒有換行。 搞了一下子發現,YYLabel還得設定一個preferredMaxLayoutWidth屬性,這個屬性是設定最大寬度,設定完才能有換行

佈局 佈局xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app=

笨鳥兒 靜態佈局、自適應佈局佈局、響應佈局、彈性佈局等的概念和區別

一、靜態佈局(Static Layout) 即傳統Web設計,網頁上的所有元素的尺寸一律使用px作為單位。 1、佈局特點:不管瀏覽器尺寸具體是多少,網頁佈局始終按照最初寫程式碼時的佈局來顯示。常規的pc的網站都是靜態(定寬度)佈局的,也就是設定了min-width,這樣的話,如果小於這個寬度就會

Flex佈局實戰(二):網格 \ 聖盃 \ 輸入框 \ 懸掛 \ 固定底欄 \ 佈局

參考:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 下面程式碼可能會比較多,但核心CSS程式碼已經用 /**/ 的標記標出,直接看核心程式碼就好。 一、網格佈局 1、基本網格佈局 最簡單

the Percentage Layout of Android (的百分比佈局

不用wrap_content.match_parent來指定 控制元件的大小, 1.在app/bulid.gradle檔案的dependencies中新增 compile 'com.android.support:percent:24.2.1(注意在新增檔案之後要同步一下) 2.修改佈局檔案xml檔案中

github中的README md快速佈局

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 自定義View-----佈局(粗糙實現)

//首先檢視一下佈局介面吧 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app

佈局 實現搜尋框

自定義View類 public class FlowLayout extends ViewGroup { public FlowLayout(Context context) { this(context,null); } public Flow