天天看點

Android基礎控件——RecyclerView實作混排效果,仿網易雲音樂前言效果示範實作分析項目結構

本篇文章已授權微信公衆号 study_tech(楊守樂)獨家釋出轉載請标明出處

前言

最近在使用網易雲音樂的時候,看到如下圖的排版效果圖,自己也想實作一個

Android基礎控件——RecyclerView實作混排效果,仿網易雲音樂前言效果示範實作分析項目結構

這裡采用網上用法最多的方式,而且是比較簡單的方式實作的,想要做項目的同學也可以快速入手搞定首頁界面,可以在最快的時間内模仿出來,且效果達到90%以上的相似

效果示範

至于圖檔的加載你們可以根據網上的Api擷取相應的圖檔加載到對應的位置,這裡隻是采用本地圖檔來示範

Android基礎控件——RecyclerView實作混排效果,仿網易雲音樂前言效果示範實作分析項目結構

實作分析

這裡是采用RecyclerView的GridLayoutManager的一個SpanSize這麼一個東西,從下圖很容易知道其意思

Android基礎控件——RecyclerView實作混排效果,仿網易雲音樂前言效果示範實作分析項目結構

項目結構

項目結構可能對初學者感覺很龐大,不用擔心,這裡我會按照下面的包名劃分,從最簡單的開始分析

Android基礎控件——RecyclerView實作混排效果,仿網易雲音樂前言效果示範實作分析項目結構

引入依賴

首先是在Gradle中引入對RecyclerView的依賴

View包

由于項目用到的圖檔是有規格限定的,是以需要對ImageView覆寫,得到我們想要尺寸的圖檔

  • SquareImageView:正方形圖檔
  • RectImageView:長方形圖檔
public class SquareImageView extends android.support.v7.widget.AppCompatImageView {

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

    public SquareImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,);
    }

    public SquareImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setScaleType(ScaleType.FIT_XY);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }
}
           
public class RectImageView extends android.support.v7.widget.AppCompatImageView {

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

    public RectImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, );
    }

    public RectImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setScaleType(ScaleType.FIT_XY);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        // 設定大小為寬度的三分之二
        int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width /  * , widthMode);
        super.onMeasure(widthMeasureSpec, halfWidthMeasureSpec);
    }
}
           

Music包

這裡是我們存儲實體的地方,其中四種類型的劃分,分别對應項目展示中的前三個子產品的劃分,其中還有一個标題也算是一種類型,是以共四種

public class Music {

    public int type;
    public String title;
    // 後期可加入Glide加載網絡圖檔Url
    public int imageId;

    public interface TYPE {
        int TYPE_GRID_THREE = ;
        int TYPE_GRID_TWO = ;
        int TYPE_LIST = ;
        int TYPE_TITLE = ;
    }
}
           

Listener包

由于RecyclerView自身是沒有點選事件的,是以這個包是RecyclerView的點選事件接口

public interface OnItemClickListener {
    void OnItemClick(int position);
}
           

Decoration包

由于RecyclerView是不提供分割線的,是以這個包是自定義的分割線

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int space;

    public SpacesItemDecoration(int space) {
        this.space = space;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.left = space;
        outRect.right = space;
        outRect.bottom = space;
        outRect.top = space;
    }
}
           

ViewHolder

這裡存儲的是我們混排效果的控件,标題可能會有點差別,其他是一樣的效果,為了後期友善拓展,我們就把他們分開,不代碼複用

public class GridThreeViewHolder extends RecyclerView.ViewHolder {

    public SquareImageView iv_icon;
    public TextView tv_content;

    public GridThreeViewHolder(View itemView) {
        super(itemView);
        iv_icon = (SquareImageView) itemView.findViewById(R.id.iv_icon);
        tv_content = (TextView) itemView.findViewById(R.id.tv_content);
    }
}
           
public class GridTwoViewHolder extends RecyclerView.ViewHolder {

    public RectImageView iv_icon;
    public TextView tv_content;

    public GridTwoViewHolder(View itemView) {
        super(itemView);
        iv_icon = (RectImageView) itemView.findViewById(R.id.iv_icon);
        tv_content = (TextView) itemView.findViewById(R.id.tv_content);
    }
}
           
public class ListViewHolder extends RecyclerView.ViewHolder {

    public RectImageView iv_icon;
    public TextView tv_content;

    public ListViewHolder(View itemView) {
        super(itemView);
        iv_icon = (RectImageView) itemView.findViewById(R.id.iv_icon);
        tv_content = (TextView) itemView.findViewById(R.id.tv_content);
    }
}
           
public class TitleViewHolder extends RecyclerView.ViewHolder {

    public TextView tv_content;

    public TitleViewHolder(View itemView) {
        super(itemView);
        tv_content = (TextView) itemView.findViewById(R.id.tv_content);
    }
}
           

Adapter包

這裡就是對所有ViewHolder的控制器,然而這裡并不是混排效果實作的最終地方,隻不過是填充資料的地方

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {

    private List<Music> mList;
    private Context mContext;
    private LayoutInflater mInflater;

    /**
     * 點選事件
     */
    private OnItemClickListener mOnItemClickListener;

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

    public RecyclerAdapter(Context context, List<Music> list) {
        this.mList = list;
        this.mContext = context;
        this.mInflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        RecyclerView.ViewHolder mViewHolder = null;
        if (viewType == Music.TYPE.TYPE_GRID_THREE) {
            view = mInflater.inflate(R.layout.item_grid_three, parent, false);
            mViewHolder = new GridThreeViewHolder(view);
        } else if (viewType == Music.TYPE.TYPE_GRID_TWO) {
            view = mInflater.inflate(R.layout.item_grid_two, parent, false);
            mViewHolder = new GridTwoViewHolder(view);
        } else if (viewType == Music.TYPE.TYPE_LIST) {
            view = mInflater.inflate(R.layout.item_list, parent, false);
            mViewHolder = new ListViewHolder(view);
        } else if (viewType == Music.TYPE.TYPE_TITLE) {
            view = mInflater.inflate(R.layout.item_title, parent, false);
            mViewHolder = new TitleViewHolder(view);
        }
        return mViewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (getItemViewType(position)) {
            case Music.TYPE.TYPE_GRID_THREE:
                GridThreeViewHolder gHolder_three = (GridThreeViewHolder) holder;
                gHolder_three.tv_content.setText(mList.get(position).title);
                gHolder_three.iv_icon.setImageResource(mList.get(position).imageId);
                //點選事件
                gHolder_three.itemView.setOnClickListener(this);
                gHolder_three.itemView.setTag(position);
                break;
            case Music.TYPE.TYPE_GRID_TWO:
                GridTwoViewHolder gHolder_two = (GridTwoViewHolder) holder;
                gHolder_two.tv_content.setText(mList.get(position).title);
                gHolder_two.iv_icon.setImageResource(mList.get(position).imageId);
                //點選事件
                gHolder_two.itemView.setOnClickListener(this);
                gHolder_two.itemView.setTag(position);
                break;
            case Music.TYPE.TYPE_LIST:
                ListViewHolder lHolder = (ListViewHolder) holder;
                lHolder.tv_content.setText(mList.get(position).title);
                lHolder.iv_icon.setImageResource(mList.get(position).imageId);
                //點選事件
                lHolder.itemView.setOnClickListener(this);
                lHolder.itemView.setTag(position);
                break;
            case Music.TYPE.TYPE_TITLE:
                TitleViewHolder tHolder = (TitleViewHolder) holder;
                tHolder.tv_content.setText(mList.get(position).title);
                //點選事件
                tHolder.itemView.setOnClickListener(this);
                tHolder.itemView.setTag(position);
                break;
        }
    }

    @Override
    public int getItemViewType(int position) {
        return mList.get(position).type;
    }

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

    @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            int position = (int) v.getTag();
            mOnItemClickListener.OnItemClick(position);
        }
    }
}
           

Activity

這裡就是我們實作混排效果的關鍵,我們會根據不同類型的資料,對RecyclerView的SpanSize的進行設定

public class MainActivity extends AppCompatActivity implements OnItemClickListener {

    private RecyclerView ry;
    private GridLayoutManager layoutManager;
    private RecyclerAdapter mAdapter;
    private static List<Music> mList;

    /**
     * 模拟本地資料
     */
    static {
        mList = new ArrayList<>();

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_TITLE;
            music.imageId = R.drawable.ic_cover;
            music.title = "推薦歌單";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_GRID_THREE;
            music.imageId = R.drawable.ic_cover;
            music.title = "先不要降溫!我沒錢買衣服";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_TITLE;
            music.imageId = R.drawable.ic_cover;
            music.title = "推薦MV";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_GRID_TWO;
            music.imageId = R.drawable.ic_cover;
            music.title = "Perfect Day";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_TITLE;
            music.imageId = R.drawable.ic_cover;
            music.title = "精選專欄";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_LIST;
            music.imageId = R.drawable.ic_cover;
            music.title = "去看《銀翼殺手2049》前,你應該知道的三件事";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_TITLE;
            music.imageId = R.drawable.ic_cover;
            music.title = "最新音樂";
            mList.add(music);
        }

        for (int i = ; i < ; i++) {
            Music music = new Music();
            music.type = Music.TYPE.TYPE_GRID_THREE;
            music.imageId = R.drawable.ic_cover;
            music.title = "[BGM]一定聽過的神級背景配樂";
            mList.add(music);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ry = (RecyclerView) findViewById(R.id.ry);
        layoutManager = new GridLayoutManager(this, );
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                int type = mList.get(position).type;
                if (type == Music.TYPE.TYPE_GRID_THREE) {
                    return ;
                } else if (type == Music.TYPE.TYPE_GRID_TWO) {
                    return ;
                } else if (type == Music.TYPE.TYPE_LIST) {
                    return ;
                } else if (type == Music.TYPE.TYPE_TITLE) {
                    return ;
                }
                return ;
            }
        });
        ry.setLayoutManager(layoutManager);
        ry.addItemDecoration(new SpacesItemDecoration());

        // 填充資料
        mAdapter = new RecyclerAdapter(this, mList);
        mAdapter.setOnItemClickListener(this);
        ry.setAdapter(mAdapter);

    }

    @Override
    public void OnItemClick(int position) {
        String title = mList.get(position).title;
        Toast.makeText(this, title, Toast.LENGTH_SHORT).show();
    }
}
           

layout布局檔案

這裡的布局很簡單,比如用到我們的正方形圖檔,長方形圖檔等,這裡就不做代碼貼出,詳細可以檢視源碼

源碼下載下傳

源碼下載下傳