1. 程式人生 > >Android5.x:RecycleView(一):實現ListView + GridView + StaggeredGridLayou效果

Android5.x:RecycleView(一):實現ListView + GridView + StaggeredGridLayou效果

1 RecycleView實現ListView的功能

需要新增依賴:

compile 'com.android.support:recyclerview-v7:24.2.0'

相關方法:

RecyclerView的方法:

方法 含義
setLayoutManager(…) 設定佈局管理者
setAdapter 設定介面卡

Adapter中的方法:

方法 含義
onCreateViewHolder(…) 建立ViewHolder
onBindViewHolder(…) 給ViewHolder裡面的view設定屬性
getItemCount item的數量

步驟

  • 找到RecycleView
  • 給RecycleView設定 LayoutManager
  • 給RecycleView設定 Adapter

下面通過一個demo顯示RecycleView的基本使用。

見圖:
這裡寫圖片描述

MainActivity

package com.cqc.recyclerview01;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import
android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private
RecyclerView recyclerView; private Context context; private List<String> list = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; initData(); recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); recyclerView.setAdapter(new MyAdapter()); } private void initData() { list.clear(); for (int i = 0; i < 100; i++) { list.add("item" + i); } } public class MyAdapter extends RecyclerView.Adapter { private MyHolder myHolder; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { TextView tv = new TextView(context); tv.setHeight(50); myHolder = new MyHolder(tv); return myHolder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { // MyHolder myHolder = (MyHolder) holder; myHolder = (MyHolder) holder; myHolder.textView.setText(list.get(position)); } @Override public int getItemCount() { return 100; } } public class MyHolder extends RecyclerView.ViewHolder { public TextView textView; public MyHolder(View itemView) { super(itemView); textView = (TextView) itemView; } } }

實現item的分割線

com.android.support:recyclerview-v7:25.0.1官方有了預設的DividerItemDecoration,25.0.1之前沒有這個類。

我們發現recyclerview沒有分割線,需要呼叫mRecyclerView.addItemDecoration()新增分割線,
但是ItemDecoration是抽象類,需要我們自己實現。

怎麼新增預設的分割線?

匯入類DividerItemDecoration到專案中,呼叫方法:

recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));

//或者
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));

注意:注意context為null的情況,儘量用getActivity()或者getContext(),最近就報了錯,找了半天才發現是這裡錯了。儘量不要讓context變成成員變數,

private  Context context = getActivity();

怎麼新增自定義分割線 ?

建立自定義的分割線:item_divider.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <size android:height="4dp"/><!--不用設定寬度-->

    <gradient
        android:centerColor="#ff0000ff"
        android:endColor="#ffff0000"
        android:startColor="#ff00ff00"
        android:type="linear"/>
</shape>
<!--recyclerView分割線的設定-->

使用自定義的分割線:styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        ...
        <!--自定義item的分割線-->
        <item name="android:listDivider">@drawable/item_divider</item>
    </style>

</resources>

效果圖:
這裡寫圖片描述

package com.cqc.recyclerview01;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

/**
 * RecyclerView新增分割線(當使用LayoutManager為LinearLayoutManager時)
 * 該類源於:http://blog.csdn.net/lmj623565791/article/details/45059587
 */
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;

    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent) {
        Log.v("recyclerview - itemdecoration", "onDraw()");

        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }

    }


    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

然後在mainActivity中呼叫:

recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));//新增分割線

新增點選事件

方法一:在ViewHolder的構造方法裡面寫點選監聽事件

static class MyViewHolder extends RecyclerView.ViewHolder {

    TextView tv;

    public MyViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int adapterPosition = getAdapterPosition();
            }
        });
    }
}

方法二:在onBindViewHolder(...)裡面寫itemView的點選監聽

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
    MyViewHolder holder = (MyViewHolder) viewHolder;
    holder.tv.setText("" + position);

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d(TAG, "position=" + position);
        }
    });
}

方法三:利用回撥,在Activity中寫點選監聽

先在adapter中設定回撥

//設定點選回撥
public interface OnItemClickListener {
    void onItemClick(View view, int position);

    void onItemLongClick(View view, int position);
}

private OnItemClickListener mOnItemClickListener;

public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
    this.mOnItemClickListener = mOnItemClickListener;
}

然後在onBindViewHolder(…)中呼叫點選事件監聽

//如果設定了回撥,則呼叫點選事件
if (mOnItemClickListener != null) {
    holder.textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mOnItemClickListener.onItemClick(v,position);
        }
    });

    holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            mOnItemClickListener.onItemLongClick(v, position);
            return true;//事件被處理
        }
    });
}

如果只有短點選,同理也需要在onBindViewHolder()中設定.

//如果設定了回撥,則呼叫點選事件
if (mOnItemClickListener != null) {
    holder.textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mOnItemClickListener.onItemClick(v,position);
        }
    });
}
public interface OnItemClickListener {
    void onItemClick(View view, int position);
}

private OnItemClickListener mOnItemClickListener;

public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
    this.mOnItemClickListener = mOnItemClickListener;
}

Mactivity中呼叫

adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {

    }
});

最後在activity中使用點選事件

//點選事件(用adapter呼叫點選方法)
adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClickListerner(View view, int position) {
        Toast.makeText(context,"短點選",Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClickListener(View view, int position) {

        Toast.makeText(context,"長點選",Toast.LENGTH_SHORT).show();
    }
});

效果圖:
這裡寫圖片描述

新增item和刪除item

方法:
新增item:adapter.notifyItemInserted(position)
刪除item:adapter.notifyItemRemoved(position);
    btn_add.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            list.add(0,"btn_add");
//                adapter.notifyDataSetChanged();//也可以,但是沒有動畫效果
            adapter.notifyItemInserted(0);
            recyclerView.scrollToPosition(0);//滑動到第一個item,不加不會滑動到頂部。
        }
    });

    btn_delete.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            list.remove(0);
            adapter.notifyDataSetChanged();//也可以,但是沒有動畫效果
//                adapter.notifyItemRemoved(0);
        }
    });

item新增/刪除的動畫

 recyclerView.setItemAnimator(new DefaultItemAnimator());//item新增移出動畫

notifyItemRemoved(int position)引起的角標越界異常

在呼叫該方法後,

list.remove(position);
adapter.notifyItemRemoved(position);

容易引起角標越界異常,因為呼叫該方法後,不會重新走onBindViewHolder(…),所以position的值沒有變化。
解決方法:

list.remove(position);
adapter.notifyItemRemoved(position);
if (position != list.size()) {//如果刪除的是最後一個,則不重新整理資料。
    adapter.notifyItemRangeChanged(position, list.size() - deleteIndex);//重新整理position後的資料
}

新增HeadView和FootView

1 這裡只討論新增一個HeadView或FootView
2 下面的是沒有給HeadView或FootView建立單獨的ViewHolder,用的是同一個ViewHolder,並在其構造方法中判斷;
3 當然也可以給HeadView或FootView建立單獨的ViewHolder, 並在onCreateViewHolder(…)中返回對應的ViewHolder。

首先定義HeadView和FootView的型別

//預設只加一個header 和footer,2種類型不能為0,因為getItemViewType(int position)預設返回0
private int HeaderType = 1;
private int FooterType = 2;

其次:定義HeadView和FootView變數,並設定和獲取他們的數量(0/1)

private View headerView;
private View footerView;

public void setHeaderView(View headerView) {
    this.headerView = headerView;
}

public void setFooterView(View footerView) {
    this.footerView = footerView;
}

public int getHeadViewCount() {
    return headerView == null ? 0 : 1;
}

public int getFootViewCount() {
    return footerView == null ? 0 : 1;
}

第三步:重寫方法getItemViewType(int position)

此處注意:定義HeaderType 和FooterType的值不能是0,因為該方法預設返回0,如果頭或尾定義了0,而你又沒有給其它item(HeadView/FootView之外的item)定義type,仍然使用return super.getItemViewType(position);,那麼0就代表2種類型了,這是不對的。

這裡寫圖片描述

這裡寫圖片描述

@Override
public int getItemViewType(int position) {
    if (position == 0) {
        return HeaderType;
    }
    if (position == 100 + getHeadViewCount()) {
        return FooterType;
    }
    return super.getItemViewType(position);
}

上面的是確定有header,其實不應該這樣判斷

@Override
public int getItemViewType(int position) {
    if (position < getHeadViewCount()) {
        return HEAD;
    }
    if (position >= list.size() + getHeadViewCount()) {
        return FOOT;
    }
    return NORMAL;
}

第四步:修改Adapter中的3個方法

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == HeaderType) {
        return new MyViewHolder(headerView);
    }
    if (viewType == FooterType) {
        return new MyViewHolder(footerView);
    }
    View itemView = View.inflate(context, android.R.layout.two_line_list_item, null);
    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    if (getItemViewType(position) == HeaderType) {
        return;
    }
    if (getItemViewType(position) == FooterType) {
        return;
    }

    MyViewHolder holder = (MyViewHolder) viewHolder;
    holder.tv1.setText("Title");
    holder.tv2.setText("message");
}

@Override
public int getItemCount() {
    return 100 + getHeadViewCount() + getFootViewCount();
}
public class MyViewHolder extends RecyclerView.ViewHolder {

    TextView tv_item;

    public MyViewHolder(View itemView) {
        super(itemView);
        if (itemView == headerView) {
            return;
        }
        if (itemView == footerView) {
            return;
        }
        tv_item = (TextView) itemView.findViewById(R.id.tv_item);
    }
}

第五步:我們新增HeadView和FootView,setHeaderView(view)放在setAdapter(adapter)前後無所謂。

recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new MyAdapter();
recyclerView.setAdapter(adapter);

View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);

adapter.setHeaderView(headView);
adapter.setFooterView(footView);

這是我們發現:HeadView或FootView的寬度沒有充滿螢幕,這是什麼原因呢? 詳見:Android基礎:三種inflate的區別
這是因為我們在填充HeadView或FootView使用的是

View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);

改成下面這種,注意:第二個引數parent是recyclerView

View headView = LayoutInflater.from(context).inflate(R.layout.layout_header,recyclerView, false);
View footView =LayoutInflater.from(context).inflate(R.layout.layout_footer, recyclerView,false);

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

setHasFixedSize(true)

recyclerView.setHasFixedSize(true);

它的作用是當我們隊資料進行增刪的時候,不會重新計算item寬高。

注意事項

1 onCreateViewHolder(…)不可以複用holder,必須new。

if (myHolder == null){
      myHolder = new MyHolder(tv);
}

2 onCreateViewHolder(…)中的holder和onBindViewHolder(RecyclerView.ViewHolder holder, int position) {。。}中的holder就是我們建立的MyHolder,可以直接進行格式轉換,也可以宣告成員變數。

    public class MyAdapter extends RecyclerView.Adapter {

        private MyHolder myHolder;

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            TextView tv = new TextView(context);
            tv.setHeight(50);
            myHolder = new MyHolder(tv);
            return myHolder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            //  MyHolder myHolder = (MyHolder) holder;
            myHolder = (MyHolder) holder;
            myHolder.textView.setText(list.get(position));
        }

        @Override
        public int getItemCount() {
            return 100;
        }
    }

3 最好先建立類ViewHolder,再建立Adapter,這樣可以使用直接指定泛型,就不需要再格式轉換了。

public class MyAdapter extends RecyclerView.Adapter<MyHolder>

4 item的高度設為match_parent,父佈局設定100dp,item的高無效

父佈局設定具體的高度,子view設match_parent,這是無效的。
方法一:必須給子view(textview設定具體的數值),父佈局math——parent
方法二:item的view:使用    LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..)) 

5 item的子view(textview)寬度match_parent,父佈局的寬度也是match_parent,但是仍然無法充滿螢幕。

方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..))

6 總結:結合4+5,使用View.inflate(context, R.layout.item_main, null);建立item的view,會導致item的view高度無效,子view(textview)的高寬無效。

方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!
錯誤:(View.inflate(context, R.layout.item_main, null) 
錯誤:LayoutInflater.from(context).inflate(R.layout.item_main,null))

7 item寬度沒有充滿螢幕

RecyclerView子View寬度不能全屏的問題,在Adapter的onCreateViewHolder建立子view的時候要把parent傳進去;

//這種inflate方式有事會導致item寬度不充滿螢幕
//View itemView = View.inflate(parent.getContext(), R.layout.item_gate_frag, null);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_gate_frag,parent,false);

複雜的RecyclerView

和listview一樣,複雜的RecyclerView即使item有多種型別。這是需要重寫adapter的2方法
getItemViewType(int position) {...} getItemCount() {...}
,定義View型別:private static int ViewTypeHead = 0; private static int ViewTypeBody = 1;,在建立ViewHolder是也需要判斷ViewType,有幾種型別就建立幾個ViewHolder

這裡寫圖片描述
以下是主要程式碼:

private static int ViewTypeHead = 0;
private static int ViewTypeBody = 1;
@Override
public int getItemViewType(int position) {
    if (position % 3 == 0) {
        return ViewTypeHead;
    } else {
        return ViewTypeBody;
    }
}

@Override
public int getItemCount() {
    return 100;
}
public class ItemHeadViewHeader extends RecyclerView.ViewHolder {
    ...
    public ItemHeadViewHeader(View itemView) {
        super(itemView);
        ...
    }
}

public class ItemBodyViewHeader extends RecyclerView.ViewHolder {
    ...
    public ItemBodyViewHeader(View itemView) {
        super(itemView);
        ...
    }
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == ViewTypeHead) {
        View itemView = View.inflate(parent.getContext(), R.layout.item_header, null);
        return new ItemHeadViewHeader(itemView);
    } else {
        View itemView = View.inflate(parent.getContext(), R.layout.item_body, null);
        return new ItemBodyViewHeader(itemView);
    }
}

RecycleView(二):實現GridView的功能

同ListView基本一樣,只是LayoutManager變成了GridLayoutManager

效果圖

這裡寫圖片描述

程式碼

recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this));
adapter = new RecyclerGridAdapter(list);
recyclerView.setAdapter(adapter);

adapter.setOnItemClickListener(new RecyclerGridAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        toast.setText(list.get(position));
        toast.show();
    }
});

分割線

RecyclerView實現GridView沒有提供預設的分割線(25.0.1開始有了),V7包中的DividerItemDecoration只是給ListView使用,而GridView應該使用自定義的DividerGridItemDecoration
DividerGridItemDecoration的下載地址

效果圖:
這裡寫圖片描述

自定義分割線

ListView的divider 只需要設定高度
GridView的divider 需要設定高度+寬度

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

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <!--listView的divider 只需要設定高度-->
    <!--gridView 的divider 需要設定高度+寬度-->
    <size android:height="4dp" android:width="4dp"/>

</shape>

item的margin無效的問題

margin無效 效果圖: margin有效 效果圖
這裡寫圖片描述 這裡寫圖片描述

要想使itemView 跟佈局的layout_margin生效,必須指定root即parent,也就是不能使用第一種方式建立itemView

//第一種
View itemView = View.inflate(parent.getContext(), R.layout.item_grid, null);

//第二種
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid, parent, false);

RecylerView實現瀑布流

效果圖

這裡寫圖片描述

同ListView和GridView的不同之處

recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
recyclerView.addItemDecoration(new DividerGridItemDecoration(StaggeredGridActivity.this));

怎麼是這種效果的?

通過設定每個item的隨機高度,達到顯示瀑布流的效果。

heights.add(random.nextInt(100) + 100);
ViewGroup.LayoutParams params = holder.tv.getLayoutParams();
params.height = heights.get(position);
holder.tv.setLayoutParams(params);

ScrollView巢狀RecylerView

下面的內容來源自android ScrollView巢狀RecyclerView ,之驗證了重寫LinearLayoutManager,可以正常使用,而且乜有重寫ScrollView

ScrollView巢狀ListView,我們一般重寫onMeasure()方法,但是RecyclerView不行,而是要重寫LayoutManager,比如LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager。但是值得注意的是,如果recyclerView很長那麼強烈不建議去做巢狀,因為這樣recyclerView會在展示的時候立刻展示所有內容,效率極低。

重寫LinearLayoutManager

public class FullyLinearLayoutManager extends LinearLayoutManager {

    private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();

    public FullyLinearLayoutManager(Context context) {
        super(context);
    }

    public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public FullyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {

        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode
                + " \nheightMode " + heightSpec
                + " \nwidthSize " + widthSize
                + " \nheightSize " + heightSize
                + " \ngetItemCount() " + getItemCount());

        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        try {
            View view = recycler.getViewForPosition(0);//fix 動態新增時報IndexOutOfBoundsException

            if (view != null) {
                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();

                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                        getPaddingLeft() + getPaddingRight(), p.width);

                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop() + getPaddingBottom(), p.height);

                view.measure(childWidthSpec, childHeightSpec);
                measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
                measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
                recycler.recycleView(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }
}

重寫GridLayoutManager

public class FullyGridLayoutManager extends GridLayoutManager {
    public FullyGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public FullyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        
            
           

相關推薦

Android5.x:RecycleView實現ListView + GridView + StaggeredGridLayou效果

1 RecycleView實現ListView的功能 需要新增依賴: compile 'com.android.support:recyclerview-v7:24.2.0' 相關方法: RecyclerView的方法:

IO流的應用實現檔案的複製

package com.bjpowernode.demo03; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; /** 使用FileReader/FileW

安卓專案實戰之強大的網路請求框架okGo使用詳解實現get,post基本網路請求,下載上傳進度監聽以及對Callback自定義的深入理解

1.新增依賴 //必須使用 compile 'com.lzy.net:okgo:3.0.4' //以下三個選擇新增,okrx和okrx2不能同時使用,一般選擇新增最新的rx2支援即可 compile 'com.lzy.net:okrx:1.0.2' compile 'com.lzy

關於wxpy的小實驗實現登入微信、訊息接收、處理、回覆和人臉檢測處理反饋

概述:本文主要是博主想分享一下最近在學習python和opencv時做的一些小實驗和作為自己程式設計之路剛開始的一個小筆記。在剛接觸python時發現了有一個叫wxpy的東西,他可以實現讓微信自動接收、

Android二維碼掃描開發實現思路與原理

【 回覆“ 1024 ”,送你一個特別推送 】 現在二維碼已經非常普及了,那麼二維碼的掃描與處理也成為了Android開發中的一個必要技能。網上有很多關於Android中二維碼處理的帖子,大都是在講開源框架zxing用法,然後貼貼程式碼就完了,並沒有一個系統的分析和

Mina、Netty、Twisted一起學實現簡單的TCP伺服器

MINA、Netty、Twisted為什麼放在一起學習?首先,不妨先分別看一下它們官方網站對其的介紹:MINA:Apache MINA is a network application framework which helps users develop high perf

SignalR 2.x入門SignalR簡單例子

本系列教程使用工具 開發工具:VS2015 .NET版本:4.5 SignalR 版本:2.x系列 建立空Asp.Net Web專案,在程式包管理器控制檯中輸入如下命令,安裝SignalR: install-package Microsoft.AspNet.Signa

分散式服務架構學習實現自己的RPC框架採用Java Socket

RPC實現原理圖: 1、Service API對應服務介面。 HelloService.java程式碼如下: package com.shan.rpc.service; public interface HelloService { public String

VST SDK 3.x 開發VST結構介紹

注:所有文章內容均可在VST SDK的doc資料夾中找到英文版。如果你英文非常好可以忽略這個系列的文章直接看原版。本文並不是翻譯而是個人對說明文件的理解 一、VST音訊外掛 VST(Virtual Sound Technology)音訊外掛是Steinberg公司創造的。簡

分布式鎖實現大型連續劇之Redis

set 但是 sss channel 時有 commands 阻塞 iss cond 前言: 單機環境下我們可以通過JAVA的Synchronized和Lock來實現進程內部的鎖,但是隨著分布式應用和集群環境的出現,系統資源的競爭從單進程多線程的競爭變成了多進程的競爭,這時

前端案例分享CSS+JS實現流星雨動畫

目錄 引言 1、效果圖 2、原始碼 3、案例解析 4、小問題 5、結語 引言        平常會做一些有意思的小案例練手,通常都會發到codepen上,但是codepen不能寫分析。  &nb

Java併發volatile的實現原理 Java併發Java記憶體模型乾貨總結

synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized volatile是一個變數修飾符,只能用來修飾變數。 volatile寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。 volatile讀:當讀一

Caffe視覺化網路結構視覺化用Caffe自帶程式實現

Caffe視覺化(一):網路結構視覺化(用Caffe自帶程式實現) 本文記錄瞭如何利用Caffe自帶的程式實現網路的視覺化,包括可能遇到的問題和解決方案。更新於2018.10.25。 文章目錄 Caffe視覺化(一):網路結構視覺化(用Caffe自帶程式實現

資料結構實現動態陣列C++版

資料結構實現(一):動態陣列(C++版) 1. 概念及基本框架 2. 基本操作程式實現 2.1 增加操作 2.2 刪除操作 2.3 修改操作 2.4 查詢操作 2.5 其他操作 3. 演算法複雜度分析

Spring Boot Actuator詳解與深入應用Actuator 1.x

《Spring Boot Actuator詳解與深入應用》預計包括三篇,第一篇重點講Spring Boot Actuator 1.x的應用與定製端點;第二篇將會對比Spring Boot Actuator 2.x 與1.x的區別,以及應用和定製2.x的端點;第三篇將會介紹Actuator metric指

JAVA高階基礎8---Set的典型實現HashSet

HHashSet 注:更多詳細方法請自行在 API 上查詢 HashSet 是由hash表(hashMap)支援,不保證元素的迭代順恆久不變,允許存在null值,元素不允許重複,同時,不是執行緒安全的 HashSet是基於HashMap實現的。   &n

用python來實現機器學習線性迴歸linear regression

需要下載一個data:auto-mpg.data 第一步:顯示資料集圖 import pandas as pd import matplotlib.pyplot as plt columns = ["mpg","cylinders","displacement","horsepowe

Asp.net Core 使用Jenkins + Dockor 實現持續整合、自動化部署Jenkins安裝

寫在前面 其實園子裡很多大佬都寫過,我也是一個搬運工很多東西不是原創的,不過還是想把自己安裝的過程,記錄下來如果能幫到大家的忙,也是一件功德無量的事; 執行環境 centos:7.2 cpu:1核 2G記憶體 1M頻寬 其實用的騰訊雲 安裝jenkins 這裡的jenkins就不從docker

java多執行緒系列Thread、Runnable、Callable實現多執行緒的區別

實現多執行緒 java實現多執行緒的方法有三種,分別是繼承thread類,實現runnable介面,實現callable介面(call方法有返回值) /** * 繼承Thread */ public class MyThread extends Thread{ int a = 0;

JAVA 學習16進位制字串自增的實現

JAVA學習系列,並不是從基礎去講java的知識,而是把我在學習或是工作中,一些思想、邏輯總結出來。 原先在工作中,因為測試的需要,經常要往資料庫中批量的插資料。而表的主鍵用的是UUID,是由16進位制字元加“-”組成的,還有裝置的mac地址是由16進位制字元加“:”組成的,那個時候,我剛學ja