欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android自定義ViewGroup實現(xiàn)九宮格布局

 更新時間:2022年12月12日 09:16:40   作者:newki  
這篇文章主要為大家詳細介紹了Android如何通過自定義ViewGroup實現(xiàn)九宮格布局,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下

前言

在之前的文章我們復(fù)習(xí)了 ViewGroup 的測量與布局,那么我們這一篇效果就可以在之前的基礎(chǔ)上實現(xiàn)一個靈活的九宮格布局。

那么一個九宮格的 ViewGroup 如何定義,我們分解為如下的幾個步驟來實現(xiàn):

  • 先計算與測量九宮格內(nèi)部的子View的寬度與高度。
  • 再計算整體九宮格的寬度和高度。
  • 進行子View九宮格的布局。
  • 對單獨的圖片和四宮格的圖片進行單獨的布局處理
  • 對填充的子View的方式進行抽取,可以自由添加布局。
  • 對自定義屬性的抽取,設(shè)置通用的屬性。

只要在前文的基礎(chǔ)上掌握了 ViewGroup 的測量與布局,其實實現(xiàn)起來一點都不難,甚至我們還能實現(xiàn)一些特別的效果。

好了,話不多說,Let's go

一、九宮格的測量

之前的文章,我們的測量方式是已經(jīng)知道子 View 的具體大小了,讓我們的父布局做寬高的適配,所以我們的邏輯順序也是先布局,然后再測量,對 ViewGroup 的寬高做限制。

但是在我們做九宮格控件的時候,就和之前有所區(qū)別了。我們不管子 View 的寬高測量模式是怎樣的,我們都是通過九宮格控件的寬度對子 View 的寬高進行強制賦值。

public class AbstractNineGridLayout extends ViewGroup {

    private static final int MAX_CHILDREN_COUNT = 9;  //最大的子View數(shù)量
    private int horizontalSpacing = 20;  //每一個Item的左右間距
    private int verticalSpacing = 20;  //每一個Item的上下間距

    private int itemWidth;
    private int itemHeight;

    public AbstractNineGridLayout(Context context) {
        this(context, null);
    }

    public AbstractNineGridLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AbstractNineGridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        for (int i = 0; i < MAX_CHILDREN_COUNT; i++) {
            ImageView imageView = new ImageView(context);
            imageView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            imageView.setBackgroundColor(Color.RED);
            addView(imageView);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int notGoneChildCount = getNotGoneChildCount();

        //不管什么模式,都是指定的固定寬高
        itemWidth = (widthSize - horizontalSpacing * 2) / 3;
        itemHeight = itemWidth;

        //measureChildren內(nèi)部調(diào)用measureChild,這里我們就可以指定寬高
        measureChildren(MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY));

        if (heightMode == MeasureSpec.EXACTLY) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        } else {

            notGoneChildCount = Math.min(notGoneChildCount, MAX_CHILDREN_COUNT);
            int heightSize = ((notGoneChildCount - 1) / 3 + 1) *
                    (itemHeight + verticalSpacing) - verticalSpacing + getPaddingTop() + getPaddingBottom();

            setMeasuredDimension(widthSize, heightSize);
        }
    }

}

剛開始的時候我們在布局初始化的時候先添加5個 mathc_parent 的9個子 View 作為測試。那么我們在布局的時候,就需要對寬度進行分割,并且強制性的測量每一個子 View 的寬高為 EXACTLY 模式。

測量完每一個子 View 之后,我們再動態(tài)的給 ViewGroup 設(shè)置寬高。

這樣測量之后的效果為:

為了方便查看效果,加上了測試的灰色背景,看著大小是符合預(yù)期的。接下來我們就開始布局。

二、九宮格的布局

在之前流式布局的 onLayout 方法中,我們是通過動態(tài)的拿到每一個子 View 的寬度去判斷當前是否會超過總寬度,是否需要換行。

而這里我們就無需這么做了,因為每一個子 View 都是固定的寬度,一行就是三個,一列最多也是三個。我們直接通過子 View 的數(shù)量就可以確定當前的行數(shù)與列數(shù)。

然后我們就能行數(shù)和列數(shù)進行布局了,具體的看代碼:

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int childCount = getChildCount();
        int notGoneChildCount = getNotGoneChildCount();
        int position = 0;

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE) {
                continue;
            }

            int row = position / 3;    //當前子View是第幾行(索引)
            int column = position % 3; //當前子View是第幾列(索引)

            //當前需要繪制的光標的X與Y值
            int x = column * itemWidth + getPaddingLeft() + horizontalSpacing * column;
            int y = row * itemHeight + getPaddingTop() + verticalSpacing * row;

            child.layout(x, y, x + itemWidth, y + itemHeight);

            //最多只擺放9個
            position++;
            if (position == MAX_CHILDREN_COUNT) {
                break;
            }
        }

    }

效果為:

如果對行和列的計算不清楚的,我們可以對每一個子 View 的位置進行回顧,總共最多也就 9 個,當為第 0 個子 View 的時候,position為 0 ,那么 position / 3 是 0,row 就是 0, position % 3 也是 0,就是第最左上角的位置了。

當為第1個子 View 的時候,position為1 ,那么 position / 3 還是0,row就是0, position % 3是1了,就是第一排中間的位置了。

只有當View超過三個之后,position /3 就是 1 了,row為 1 之后,才是第二行的位置。依次類推就可以定位到每一個子 View 需要繪制的位置。

而 x 與 y 的值與計算邏輯,我們可以想象為需要繪制當前 View 的時候,當前畫筆需要所在的位置。加上左右和上下的間距之后,我們通過這樣的方式也可以實現(xiàn) margin 的效果。還記得前文流式布局是怎么實現(xiàn) margin 效果的嗎?殊途同歸的效果。

最后具體的 child.layout 反而是最簡單的,只需要繪制子 View 本身的寬高即可。

三、單圖片與四宮格的單獨處理

一般來說我們需要單獨的處理一張圖片與四張圖片的邏輯。包括測量與布局都需要單獨的處理。

一張圖片的時候,我們需要通過方法單獨的指定圖片的寬度與高度。而四張圖片我們需要固定兩行的高度即可。

public class AbstractNineGridLayout extends ViewGroup {

    private static final int MAX_CHILDREN_COUNT = 9;  //最大的子View數(shù)量
    private int horizontalSpacing = 20;  //每一個Item的左右間距
    private int verticalSpacing = 20;  //每一個Item的上下間距
    private boolean fourGridMode = true;  //是否支持四宮格模式
    private boolean singleMode = true;  //是否支持單布局模式
    private boolean singleModeScale = true;  //是否支持單布局模式按比例縮放
    private int singleWidth;
    private int singleHeight;

    private int itemWidth;
    private int itemHeight;


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int notGoneChildCount = getNotGoneChildCount();

        if (notGoneChildCount == 1 && singleMode) {
            itemWidth = singleWidth > 0 ? singleWidth : widthSize;
            itemHeight = singleHeight > 0 ? singleHeight : widthSize;
            if (itemWidth > widthSize && singleModeScale) {
                itemWidth = widthSize;  //單張圖片先定寬度。
                itemHeight = (int) (widthSize * 1f / singleWidth * singleHeight);  //根據(jù)寬度計算高度
            }
        } else {
            //除了單布局模式,其他的都是指定的固定寬高
            itemWidth = (widthSize - horizontalSpacing * 2) / 3;
            itemHeight = itemWidth;
        }

        ...
    }

    /**
     * 設(shè)置單獨布局的寬和高
     */
    public void setSingleModeSize(int w, int h) {
        if (w != 0 && h != 0) {
            this.singleMode = true;
            this.singleWidth = w;
            this.singleHeight = h;
        }
    }
}

測量的時候我們對單布局進行測量,并且對超過寬度的一些布局做等比例的縮放。然后再測量父布局。

findViewById<AbstractNineGridLayout>(R.id.nine_grid).setSingleModeSize(dp2px(200f), dp2px(400f))

效果:

而如果是四宮格模式,我們好像也不需要重新測量,反正也是二行的高度,但是布局的時候我們需要處理一下,不然第三個子 View 的位置就會不對了。我們只需要修改x 與 y的計算方式,它們是根據(jù)行和列動態(tài)計算你的,那么修改行和列的計算方式即可。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int childCount = getChildCount();
        int notGoneChildCount = getNotGoneChildCount();
        int position = 0;

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE) {
                continue;
            }

            int row = position / 3;    //當前子View是第幾行(索引)
            int column = position % 3; //當前子View是第幾列(索引)

            if (notGoneChildCount == 4 && fourGridMode) {
                row = position / 2;
                column = position % 2;
            }

            //當前需要繪制的光標的X與Y值
            int x = column * itemWidth + getPaddingLeft() + horizontalSpacing * column;
            int y = row * itemHeight + getPaddingTop() + verticalSpacing * row;

            child.layout(x, y, x + itemWidth, y + itemHeight);

            //最多只擺放9個
            position++;
            if (position == MAX_CHILDREN_COUNT) {
                break;
            }
        }

    }

    /**
     * 單獨設(shè)置是否支持四宮格模式
     */
    public void setFourGridMode(boolean enable) {
        this.fourGridMode = enable;
    }

這樣我們就可以支持四宮格的布局模式,效果如下:

到此,我們的九宮格控件大體上是完工了,但是還不夠靈活,內(nèi)部的子 View 都是我們自己 new 出來的,我們接下來就要暴露出去讓其可以自定義布局。

四、自定義布局的抽取

如何把填充布局的邏輯抽取出來呢?一般分為兩種思路:

  • 每次初始化九宮格的時候就把九個布局全部添加進來,先測量布局了再說,然后通過暴露的方法隱藏多余的布局。
  • 通過一個定義一個數(shù)據(jù)適配器Adapter,內(nèi)部封裝一些邏輯,讓具體實現(xiàn)的類去完成具體的邏輯。

兩種方法都可以,沒有好壞之分。但是使用數(shù)據(jù)適配器的方案由于內(nèi)部的View會少,性能會好那么一丟丟,總體來說差別不大。

4.1 先布局再隱藏的思路

一般我們在抽象的九宮格類中就需要暴露這兩個重要方法,一個是填充子布局的,一個是填充數(shù)據(jù)并且隱藏多余的布局。

    //子類去實現(xiàn)-填充布局文件
    protected abstract void fillChildView();

    //子類去實現(xiàn)-對布局文件賦值數(shù)據(jù)(一般專門去給adapter去調(diào)用的)
    public abstract void renderData(T data);

例如我們的實現(xiàn)類:

    @Override
    protected void fillChildView() {
        inflateChildLayout(R.layout.item_image_grid);

        imageViews = findInChildren(R.id.iv_image, ImageView.class);
    }

    @Override
    public void renderData(List<ImageInfo> imageInfos) {

        setSingleModeSize(imageInfos.get(0).getImageViewWidth(), imageInfos.get(0).getImageViewHeight());

        setDisplayCount(imageInfos.size());

        for (int i = 0; i < imageInfos.size(); i++) {
            String url = imageInfos.get(i).getThumbnailUrl();

            ImageView imageView = imageViews[i];

            //使用自定義的Loader加載
            mImageLoader.onDisplayImage(getContext(), imageView, url);

            //點擊事件
            setClickListener(imageView, i, imageInfos);
        }
    }

重點是填充的方法 inflateChildLayout 分為兩種情況,一種是布局都一樣的情況,一種是根據(jù)索引填充不同的布局情況。

    /**
     * 可以為每一個子布局加載對應(yīng)的布局文件(不同的文件)
     */
    protected void inflateChildLayoutCustom(ViewGetter viewGetter) {
        removeAllViews();
        for (int i = 0; i < MAX_CHILDREN_COUNT; i++) {
            addView(viewGetter.getView(i));
        }
    }

    /**
     * 一般用這個方法填充布局,每一個小布局的布局文件(相同的文件)
     */
    protected void inflateChildLayout(int layoutId) {
        removeAllViews();
        for (int i = 0; i < MAX_CHILDREN_COUNT; i++) {
            LayoutInflater.from(getContext()).inflate(layoutId, this);
        }
    }

而我們設(shè)置數(shù)據(jù)的方法中調(diào)用的 setDisplayCount 方法則是隱藏多余的控件的。

    /**
     * 設(shè)置顯示的數(shù)量
     */
    public void setDisplayCount(int count) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).setVisibility(i < count ? VISIBLE : GONE);
        }
    }

效果:

4.2 數(shù)據(jù)適配器的思路

而使用數(shù)據(jù)適配器的方案,就無需每次上來就先填充9個子布局,而是通過Adapter動態(tài)的配置當前需要填充的數(shù)量,并且創(chuàng)建對應(yīng)的子 View 和綁定對應(yīng)的子 View 的數(shù)據(jù)。

聽起來是不是很像RV的Apdater,沒錯就是參考它的實現(xiàn)方式。

我們先創(chuàng)建一個基類的Adapter:

    public static abstract class Adapter {

        //返回總共子View的數(shù)量
        public abstract int getItemCount();

        //根據(jù)索引創(chuàng)建不同的布局類型,如果都是一樣的布局則不需要重寫
        public int getItemViewType(int position) {
            return 0;
        }

        //根據(jù)類型創(chuàng)建對應(yīng)的View布局
        public abstract View onCreateItemView(Context context, ViewGroup parent, int itemType);

        //可以根據(jù)類型或索引綁定數(shù)據(jù)
        public abstract void onBindItemView(View itemView, int itemType, int position);

    }

然后我們需要暴露一個方法,設(shè)置Adapter,設(shè)置完成之后我們就可以添加對應(yīng)的布局了。

 public void setAdapter(Adapter adapter) {
        mAdapter = adapter;
        inflateAllViews();
    }

    private void inflateAllViews() {
        removeAllViewsInLayout();

        if (mAdapter == null || mAdapter.getItemCount() == 0) {
            return;
        }

        int displayCount = Math.min(mAdapter.getItemCount(), MAX_CHILDREN_COUNT);

        //單布局處理
        if (singleMode && displayCount == 1) {
            View view = mAdapter.onCreateItemView(getContext(), this, -1);
            addView(view);
            requestLayout();
            return;
        }

        //多布局處理
        for (int i = 0; i < displayCount; i++) {
            int itemType = mAdapter.getItemViewType(i);

            View view = mAdapter.onCreateItemView(getContext(), this, itemType);
            view.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            addView(view);
        }
        requestLayout();
    }

需要注意的是我們再測量的布局的時候,如果沒有 Adpter 或者沒有子布局的時候,我們需要單獨處理一下九宮格ViewGroup的高度。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int notGoneChildCount = getNotGoneChildCount();

        if (mAdapter == null || mAdapter.getItemCount() == 0 || notGoneChildCount == 0) {
            setMeasuredDimension(widthSize, 0);
            return;
        }

        ...
    }

那么如何綁定布局呢?在我們 onLayout完成之后我們就可以綁定數(shù)據(jù)了。

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        ...

        performBind();

    }

    /**
     * 布局完成之后綁定對應(yīng)的數(shù)據(jù)到對應(yīng)的ItemView
     */
    private void performBind() {

        if (mAdapter == null || mAdapter.getItemCount() == 0) {
            return;
        }

        post(() -> {

            for (int i = 0; i < getNotGoneChildCount(); i++) {
                int itemType = mAdapter.getItemViewType(i);
                View view = getChildAt(i);

                mAdapter.onBindItemView(view, itemType, i);
            }

        });

    }

具體的實現(xiàn)就是在 Adapter 中實現(xiàn)了。

例如我們創(chuàng)建一個最簡單的圖片九宮格適配器。

public class ImageNineGridAdapter extends AbstractNineGridLayout.Adapter {
    private List<String> mDatas = new ArrayList<>();

    public ImageNineGridAdapter(List<String> data) {
        mDatas.addAll(data);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public View onCreateItemView(Context context, ViewGroup parent, int itemType) {
        return LayoutInflater.from(context).inflate(R.layout.item_img, parent, false);
    }

    @Override
    public void onBindItemView(View itemView, int itemType, int position) {

        itemView.findViewById(R.id.iv_img).setBackgroundColor(Color.RED);
    }

}

在Activity中設(shè)置對應(yīng)的數(shù)據(jù)適配器:

     findViewById<AbstractNineGridLayout>(R.id.nine_grid).run {
            setSingleModeSize(dp2px(200f), dp2px(400f))
            setAdapter(ImageNineGridAdapter(imgs))
        }

我們就能得到同樣的效果:

如果想九宮格內(nèi)使用不同的布局,不同的索引展示不同的邏輯,都可以很方便的實現(xiàn):

public class ImageNineGridAdapter extends AbstractNineGridLayout.Adapter {
    private List<String> mDatas = new ArrayList<>();

    public ImageNineGridAdapter(List<String> data) {
        mDatas.addAll(data);
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 1) {
            return 10;
        } else {
            return 0;
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public View onCreateItemView(Context context, ViewGroup parent, int itemType) {
        if (itemType == 0) {
            return LayoutInflater.from(context).inflate(R.layout.item_img, parent, false);
        } else {
            return LayoutInflater.from(context).inflate(R.layout.item_img_icon, parent, false);
        }

    }

    @Override
    public void onBindItemView(View itemView, int itemType, int position) {

        if (itemType == 0) {
            itemView.findViewById(R.id.iv_img).setBackgroundColor(position == 0 ? Color.RED : Color.YELLOW);
        }

    }

}

效果:

到這里我們的控件就基本上能實現(xiàn)大部分業(yè)務(wù)需求了,接下來我會對一些屬性與配置進行抽取,并開源上傳到云端。

到此這篇關(guān)于Android自定義ViewGroup實現(xiàn)九宮格布局的文章就介紹到這了,更多相關(guān)Android ViewGroup九宮格布局內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • android實現(xiàn)滑動標簽頁效果的代碼解析

    android實現(xiàn)滑動標簽頁效果的代碼解析

    這篇文章主要介紹了android實現(xiàn)滑動標簽頁效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • Kotlin Service實現(xiàn)消息推送通知過程

    Kotlin Service實現(xiàn)消息推送通知過程

    這幾天分析了一下的啟動過程,于是乎,今天寫一下Service使用; 給我的感覺是它并不復(fù)雜,千萬不要被一坨一坨的代碼嚇住了,雖然彎彎繞繞不少,重載函數(shù)一個接著一個,就向走迷宮一樣,但只要抓住主線閱讀,很快就能找到出口
    2022-12-12
  • 代碼從windows下visual studio到andriod平臺遷移實現(xiàn)步驟

    代碼從windows下visual studio到andriod平臺遷移實現(xiàn)步驟

    這篇文章主要介紹了代碼從windows下visual studio到andriod平臺遷移的修改記錄的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • Android自定義水平進度條的圓角進度

    Android自定義水平進度條的圓角進度

    這篇文章主要為大家詳細介紹了Android自定義水平進度條的圓角進度,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Android實現(xiàn)知乎選項卡動態(tài)隱藏效果實例

    Android實現(xiàn)知乎選項卡動態(tài)隱藏效果實例

    選項卡相信對大家來說應(yīng)該不陌生,最近發(fā)現(xiàn)知乎選項卡的動態(tài)隱藏效果不錯,下面這篇文章主要給大家介紹了關(guān)于Android實現(xiàn)知乎選項卡動態(tài)隱藏效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-02-02
  • Flutter+Metal實現(xiàn)圖像處理詳細流程

    Flutter+Metal實現(xiàn)圖像處理詳細流程

    Flutter使用CVPixelBuffer和iOS交互,我們可以直接使用CVPixelBuffer創(chuàng)建MTLTexture,然后將MTLTexture設(shè)置為渲染目標,這篇文章主要介紹了Flutter+Metal實現(xiàn)圖像處理,需要的朋友可以參考下
    2022-06-06
  • Android ANR無響應(yīng)分析解決方案

    Android ANR無響應(yīng)分析解決方案

    這篇文章主要為大家介紹了Android ANR無響應(yīng)分析解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Android仿QQ好友列表分組實現(xiàn)增刪改及持久化

    Android仿QQ好友列表分組實現(xiàn)增刪改及持久化

    這篇文章主要介紹了Android仿QQ好友列表分組實現(xiàn)增刪改及持久化的相關(guān)資料,需要的朋友可以參考下
    2016-01-01
  • android藍牙簡單開發(fā)示例教程

    android藍牙簡單開發(fā)示例教程

    大家好,本篇文章主要講的是android藍牙簡單開發(fā)示例教程,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2021-12-12
  • Android 使用AsyncTask實現(xiàn)多線程斷點續(xù)傳

    Android 使用AsyncTask實現(xiàn)多線程斷點續(xù)傳

    本文將詳細講解如何使用AsyncTask來實現(xiàn)多線程的斷點續(xù)傳下載功能,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧
    2018-05-05

最新評論