1. 程式人生 > >Android中常見的流式佈局的使用

Android中常見的流式佈局的使用

Android中常見的自定義FlowLayout流式佈局的使用

在日常的app使用中,我們會在Android 的app中看見,比如淘寶購物頁面尺寸的選取,脈脈和慕課技術職位的選取等等熱門標籤自動換行的流式佈局,今天,我們就來看看如何自定義一個類似熱門標籤那樣的流式佈局吧,老規矩,直接上效果圖

這裡寫圖片描述

概述

1.流式佈局原理:

在佈局內,隨意擺放任意個view,每行所擺放的view個數,根據實施計算出來的寬度,一旦當前要擺放的view寬度和之前擺放的所有view寬度加在一起,超過了佈局的寬度,那麼就把該view換行擺放

2.應用場景:

一般,像這種流式佈局會應用在一些熱門標籤,熱門推薦之類的應用上

3.測量模式:

談到FlowLayout流式佈局,不得不提及他的測量模式:

* MeasureSpec.EXACTLY:精確模式, eg:100dp,match_parent.(明確指出)
* MeasureSpec.AT_MOST: 至多模式, view最多可以獲得的寬高值,它需要計算所有包含的子view的寬高,最後計算出來的寬高總和值,eg:wrap_content.
* UNSPECIFIED:未指定模式,想設定多寬多高,就給你多寬多高,一般的控制元件不會指定這種模式,但也存在,這種模式用的不多。eg:scrollview的寬高測量,就是使用的此種模式

4.在我們的流式佈局內,應該怎麼設定佈局的寬高呢? onMeasure()

1:如果佈局指定的寬是match_parent或者精確的寬度值,那麼直接就可以從父控制元件傳入的測量規格中直接獲取佈局寬度,高度同理.

2:如果佈局指定的寬高不是EXACTLY,而是AT_MOST,那麼這時候,就需要計算每一個子view的寬高,來決定佈局的寬高了。

寬度:擺放的所有子view佔據寬度最多的一行,作為佈局寬度。

高度:擺放的所有子view總共佔據幾行的高度總和。

5.子View的佈局方式: onLayout()

使用onLayout():設定ViewGroup內包含的所有子view的位置;
獲取到每一行的每一個子view,計算出它的left,top,right,bottom,呼叫layout方法設定其在流式佈局當中的位置。

  • 寬度=子view最多的那行的寬度=那一行每一個子view的寬度+leftMargin+rightMargin;

  • 高度=所有行的高度 = 每一行的高度+topMargin+bottomMargin;

LayoutParams引數的設定

ViewGroup LayoutParams :每個 ViewGroup 對應一個 LayoutParams; 即 ViewGroup -> LayoutParams
getLayoutParams 不知道轉為哪個對應的LayoutParams ,其實很簡單,就是如下:
子View.getLayoutParams 得到的LayoutParams對應的就是 子View所在的父控制元件的LayoutParams;
例如,LinearLayout 裡面的子view.getLayoutParams ->LinearLayout.LayoutParams
所以 咱們的FlowLayout 也需要一個LayoutParams,由於上面的效果圖是子View的 margin,
所以應該使用MarginLayoutParams。即FlowLayout->MarginLayoutParams

自定義ViewGroup的實現流式佈局

根據上面的技術分析,自定義類繼承於ViewGroup,並重寫 onMeasure和onLayout等方法。具體實現程式碼如下:

package com.example.administrator.p2pinvest.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;
//自定義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);
    }

    //佈局:給每一個子view佈局,childView.layout(l,t,r,b)
    private List<Integer> allHeights = new ArrayList<>();//集合中的元素:記錄每一行的高度
    private List<List<View>> allViews = new ArrayList<>();//外層集合中的元素:由每行元素構成的集合

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = this.getWidth();//得到父檢視的寬度

        int lineWidth = 0;
        int lineHeight = 0;

        // 一、給集合元素賦值
        int childCount = getChildCount();
        List<View> lineList = new ArrayList<>();//一行元素構成的集合
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //子檢視的寬高
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();
            //獲取檢視的邊距
            MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
            if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin < width) {//不換行
                lineList.add(childView);//新增子檢視到集合中
                lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + mp.topMargin + mp.bottomMargin);
            } else {//換行
                allViews.add(lineList);
                allHeights.add(lineHeight);

                //換行以後需要執行的情況
                lineList = new ArrayList<>();
                lineList.add(childView);
                lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = childHeight + mp.topMargin + mp.bottomMargin;
            }

            if (i == childCount - 1) {//如果最後一個元素
                allViews.add(lineList);
                allHeights.add(lineHeight);
            }
        }


        Log.e("TAG", "allViews.size()==" + allViews.size() + "allHeights.size()==" + allHeights.size());

        //二、遍歷集合元素,呼叫元素的layout()

        int x = 0;
        int y = 0;

        for (int i = 0; i < allViews.size(); i++) {
            List<View> lineViews = allViews.get(i);//獲取每一行的集合
            for (int j = 0; j < lineViews.size(); j++) {
                View childView = lineViews.get(j);//獲取一行的指定的j位置

                MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
                //計算的到left,top,right,bottom
                int left = x + mp.leftMargin;
                int top = y + mp.topMargin;
                int right = left + childView.getMeasuredWidth();
                int bottom = top + childView.getMeasuredHeight();

                childView.layout(left, top, right, bottom);

                //重新賦值x,y
                x += childView.getMeasuredWidth() + mp.leftMargin + mp.rightMargin;
            }

            //換行
            x = 0;
            y += allHeights.get(i);
        }
    }

    //測量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //獲取寬度和高度的佈局的數值,以及各自的設計模式,精確模式,至多模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //聲明當前檢視的寬和高,如果是至多模式,需要計算出此兩個變數的值
        int width = 0;
        int height = 0;

        //宣告每行的寬度和高度
        int lineWidth = 0;
        int lineHeight = 0;

        int childCount = getChildCount();//獲取子檢視的個數
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);

            //為了保證能夠獲取子檢視的測量的寬高,需要調下面的方法
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            //獲取子檢視測量的寬高
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();

            //獲取檢視的邊距
            MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();

            if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= widthSize) {//不換行
                lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + mp.topMargin + mp.bottomMargin);
            } else {//換行
                width = Math.max(width, lineWidth);
                height += lineHeight;

                //重新賦值
                lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = childHeight + mp.topMargin + mp.bottomMargin;

            }
            //單獨的考慮一下最後一個!因為最後一個元素並沒有計算進去
            if (i == childCount - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }

        Log.e("TAG", "width ==" + width + ",height==" + height);
        Log.e("TAG", "widthSize ==" + widthSize + ",heightSize==" + heightSize);

        //呼叫此方法,設定當前佈局的寬高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
                heightMode == MeasureSpec.EXACTLY ? heightSize : height);

    }
    //FlowLayout中有了如下的方法,在onMeasure()中可通過child就可以getLayoutParams()
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        MarginLayoutParams mp = new MarginLayoutParams(getContext(), attrs);
        return mp;

    }

}

在佈局檔案中加入自定義的flowLayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.administrator.p2pinvest.ui.FlowLayout
        android:id="@+id/flow_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.example.administrator.p2pinvest.ui.FlowLayout>
</LinearLayout>

初始化佈局使用的是butterknife

 @Bind(R.id.flow_layout)
 FlowLayout flowLayout;

提供頁面要顯示的資料,這個資料也可以放在伺服器中進行聯網獲取

    private String[] datas = new String[]{"新手計劃", "樂享活系列90天計劃", "錢包", "30天理財計劃(加息2%)",
            "林業局投資商業經營與大撈一筆", "中學老師購買車輛", "屌絲下海經商計劃", "新西遊影視拍",
            "Java培訓老師自己週轉", "HelloWorld", "C++-C-ObjectC-java", "Android vs ios", "演算法與資料結構", "JNI與NDK", "team working"};
    //初始化隨機
    private Random random;

在其他類中直接進行呼叫即可

    @Override
    public void initData(String content) {

        random = new Random();
        for(int i = 0; i < datas.length; i++) {
            final TextView textView = new TextView(getActivity());
            textView.setText(datas[i]);
            //提供邊距的物件,並設定到textView中
            ViewGroup.MarginLayoutParams mp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
            mp.leftMargin = UIUtils.dp2px(8);
            mp.rightMargin = UIUtils.dp2px(8);
            mp.topMargin = UIUtils.dp2px(8);
            mp.bottomMargin = UIUtils.dp2px(8);
            textView.setLayoutParams(mp);

            //設定背景
            //設定textView的背景
            int red = random.nextInt(211);
            int green = random.nextInt(211);
            int blue = random.nextInt(211);
            //方式一:
//            textView.setBackground(DrawUtils.getDrawable(Color.rgb(red, green, blue),UIUtils.dp2px(5)));

            //方式二:
            //儲存按下能顯示selector的效果,需要設定一個如下的屬性
            textView.setBackground(DrawUtils.getSelector(DrawUtils.getDrawable(Color.rgb(red, green, blue),UIUtils.dp2px(5)),DrawUtils.getDrawable(Color.WHITE,UIUtils.dp2px(5))));
            //方式一:
//            textView.setClickable(true);

            //新增點選事件,也是實現顯示selector的效果的一種方式
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(ProductHotFragment.this.getActivity(), textView.getText(), Toast.LENGTH_SHORT).show();
                }
            });

            //設定邊距
            //設定內邊距
            int padding = UIUtils.dp2px(10);
            textView.setPadding(padding, padding, padding, padding);


            // 2.新增到FlowLayout佈局中
            flowLayout.addView(textView);
        }
    }

用到的工具類

    public class DrawUtils {
    //提供一個指定顏色和圓角半徑的Drawable物件
    public static GradientDrawable getDrawable(int rgb,float radius){
        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setColor(rgb);//設定顏色
        gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);//設定顯示的樣式
        gradientDrawable.setCornerRadius(radius);//設定圓角的半徑
        gradientDrawable.setStroke(UIUtils.dp2px(1),rgb);//描邊
        return gradientDrawable;
    }

    public static StateListDrawable getSelector(Drawable normalDrawable,Drawable pressDrawable) {
        StateListDrawable stateListDrawable = new StateListDrawable();
        //給當前的顏色選擇器新增選中圖片指向狀態,未選中圖片指向狀態
        stateListDrawable.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, pressDrawable);
        stateListDrawable.addState(new int[]{android.R.attr.state_enabled}, normalDrawable);
        //設定預設狀態
        stateListDrawable.addState(new int[]{}, normalDrawable);
        return stateListDrawable;
    }
}

相關推薦

Android 自定義佈局(快速實現)

首先先寫一個自定義的類繼承viewgroup,程式碼如下 package com.demo.com.jd_zhang.ui.customview; import android.content.Context; import android.util.AttributeS

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

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

安卓使用佈局實現標籤

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

android自定義佈局解析與原始碼

  今天給大家解析一下自定義流式佈局的編寫,以及分析一下寫程式碼過程遇到的難點。該佈局支援水平垂直方向和子view gravity選擇,先看一下執行的效果,左邊是垂直佈局,右邊是水平佈局,套一個scrollview就支援滑動了 說一下遇

解決:Android常見的熱門標籤的佈局flowlayout不能wrap_content

最近在專案中藥使用流式佈局,但是在網上找的都不能滿足要求,這篇部落格內容只支援match_parent,我改後的程式碼可以支援wrap_content,原文也僅僅是少加一行高度而已。。新部落格希望大家多多評論。。原文連結 一:概述: 1.流式佈局的特點以

Android常見佈局的使用

Android中常見的自定義FlowLayout流式佈局的使用 在日常的app使用中,我們會在Android 的app中看見,比如淘寶購物頁面尺寸的選取,脈脈和慕課技術職位的選取等等熱門標籤自動換行的流式佈局,今天,我們就來看看如何自定義一個類似熱門標籤

Android的封裝佈局FlowLayout

鴻洋的GitHub:https://github.com/hongyangAndroid/FlowLayout 第一步:加依賴 implementation 'com.hyman:flowlayout-lib:1.1.2' 第二步:建立一個Adapter繼承TagAdapter pu

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

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

Android : 自定義View之佈局

寫了一個很簡單的佈局 這是周圍圓框的drawable <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android">

Android開發佈局關聯資料庫

效果如下 自定義View組合控制元件header_View <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res

Android-超簡單的佈局

流式佈局,一般在商城類的專案中用到會非常多比如 淘寶中,購物選擇商品列表的時候,這個就是流式佈局    創作起來也很簡單, 只要你計算出寬度,和高度,如果超出螢幕寬度,則換行擺放即可 然後我就嘗試著寫了一下,果然還是可以的 效果圖 核心方法主要是

JavaGUI簡介、AWT概述、以及佈局管理器(佈局管理器、邊界佈局管理器、網格佈局管理器、網格包佈局管理器、卡片佈局管理器)

1 GUI簡介   GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位

Android 實現一個簡易橫向佈局

SimpleFlowLayout:一個簡易的橫向流式佈局,只實現核心功能,使用者可自行擴充套件   Demo圖片如下所示: SimpleFlowLayout直接繼承自ViewGroup,主要負責

android自定義View實現佈局

//先來一張效果圖 //自定義的控制元件 import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.

[Android]FlowLayout:佈局的應用

一、應用 流式佈局即控制元件根據ViewGroup的寬,自動的往右新增,如果當前行剩餘空間不足,則自動新增到下一行。經常應用於搜尋歷史以及熱搜等介面。 二、實現 1.FlowLayout.java 只要是重寫onMeasure和onLayout兩個函式。 onMeasu

Android學習之RecyclerView學習(實現瀑布佈局

RecyclerView,大家可以通過匯入support-v7對其進行使用。  如果使用AndroidStudio開發, 需要在build.gradle中新增: compile 'com.android.support:appcompat-v7:24.2.1' com

android佈局、待辦事項應用、貝塞爾曲線、MVP+Rxjava+Retrofit、藝術圖片應用等原始碼

Android精選原始碼 android模仿淘寶首頁效果原始碼 一款藝術圖片應用,採用T-MVVM打造 Android MVP + RxJava + Retrofit專案 android流式佈局實現熱門標籤效果 android仿淘寶客戶端商品詳

Android常見佈局

android中常見佈局 [1]線性佈局 水平 垂直. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/andro

Android開發之RecyclerView實現佈局

RecyclerView是什麼? RecycleView的出現, 替代了ListView, 沒了OnitemClickListener,; LayoutManager負責計算佈局; Adapter 負責適配,還增加了ViewHolder;RecycleView

Android 佈局FlowLayout 實現關鍵字標籤

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