1. 程式人生 > >選單ListView聯動內容RecyclerView(帶吸頂效果)

選單ListView聯動內容RecyclerView(帶吸頂效果)

  這兩天測試發的bug修得差不多了,有點屬於自己的時間,寫了個仿美團/京東的ListView聯動Demo,現供大家參考參考,如有bug或更好的實現方式,望大家多多指出。
先來看看效果
這裡寫圖片描述

  一、首先我們來看看MainActivity.java 這裡有data和view的處理。data為測試用的,view為左邊一個ListView以及介面卡AdapterLeft和右邊RecyclerView和介面卡AdapterRight,提醒下RecyclerView別忘了setLayoutManager()。

package com.zyf.linkagelistview;

import android.app.Activity;
import
android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import com.zyf.linkagelistview.adapter.AdapterLeft; import com.zyf.linkagelistview.adapter.AdapterRight; import
com.zyf.linkagelistview.bean.Bean; import java.util.ArrayList; public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getSimpleName(); private ListView mListViewLeft; private AdapterLeft mAdapterLeft; private RecyclerView mListViewRight; private
AdapterRight mAdapterRight; private ArrayList<Bean> dataList = new ArrayList<>(); private ArrayList<String> titleList = new ArrayList<>(); private ArrayList<Integer> titlePosList = new ArrayList<>(); private String mCurTitle = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView(){ mListViewLeft = (ListView) findViewById(R.id.listview_left); mAdapterLeft = new AdapterLeft(this, titleList); mListViewLeft.setAdapter(mAdapterLeft); mListViewLeft.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) { mAdapterLeft.setSelection(pos); if (null != titleList && titleList.size()>pos) mAdapterRight.setSelection(pos); } }); mListViewRight = (RecyclerView) findViewById(R.id.listview_right); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mListViewRight.setLayoutManager(linearLayoutManager); mAdapterRight = new AdapterRight(this, dataList, titlePosList, mListViewRight); mListViewRight.addItemDecoration(new ItemDecoration(this, dataList, new ItemDecoration.OnDecorationCallback() { @Override public String onGroupId(int pos) { if (dataList.get(pos).getTitle() != null) return dataList.get(pos).getTitle(); return "-1"; } @Override public String onGroupFirstStr(int pos) { if (dataList.get(pos).getTitle() != null) return dataList.get(pos).getTitle(); return ""; } @Override public void onGroupFirstStr(String title) { for (int i=0; i<titleList.size(); i++){ if (!mCurTitle.equals(title) && title.equals(titleList.get(i))){ mCurTitle = title; mAdapterLeft.setSelection(i); // 設定左邊ListView選中item Log.i(TAG, "onGroupFirstStr: i = "+i); } } } })); mListViewRight.setAdapter(mAdapterRight); } /** * 資料 */ private void initData(){ titlePosList.add(0); for (int i=0; i<5; i++){ Bean bean = new Bean(); bean.setTitle("0"); bean.setText("zzzz"); dataList.add(bean); } titleList.add(dataList.get(dataList.size()-1).getTitle()); titlePosList.add(dataList.size()); for (int i=0; i<15; i++){ Bean bean = new Bean(); bean.setTitle("1"); bean.setText("xxxx"); dataList.add(bean); } titleList.add(dataList.get(dataList.size()-1).getTitle()); titlePosList.add(dataList.size()); for (int i=0; i<20; i++){ Bean bean = new Bean(); bean.setTitle("2"); bean.setText("cccc"); dataList.add(bean); } titleList.add(dataList.get(dataList.size()-1).getTitle()); titlePosList.add(dataList.size()); for (int i=0; i<10; i++){ Bean bean = new Bean(); bean.setTitle("3"); bean.setText("dddd"); dataList.add(bean); } titleList.add(dataList.get(dataList.size()-1).getTitle()); mAdapterLeft.notifyDataSetChanged(); mAdapterRight.notifyDataSetChanged(); } }

其佈局也就一個ListView和一個RecyclerView

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

    <ListView
        android:id="@+id/listview_left"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:scrollbars="none"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/listview_right"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"/>

</LinearLayout>

  二、資料需要的Bean

package com.zyf.linkagelistview.bean;

/**
 * Created by zyf on 2017/5/8.
 */

public class Bean {

    private String title;
    private String text;

    public Bean() {
    }

    public Bean(String title, String text) {
        this.title = title;
        this.text = text;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return "Bean{" +
                "title='" + title + '\'' +
                ", text='" + text + '\'' +
                '}';
    }
}

三、兩個介面卡

  1、左邊ListView介面卡AdapterLeft.java,方法setSelection(int selection)設定選中其中的item。此處佈局就一個TextView就不貼布局程式碼了。

package com.zyf.linkagelistview.adapter;

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.zyf.linkagelistview.R;

import java.util.ArrayList;

/**
 * Created by zyf on 2017/5/8.
 * 左邊ListView介面卡
 */

public class AdapterLeft extends BaseAdapter {

    private static final String TAG = AdapterLeft.class.getSimpleName();
    private Context mContext;
    private ArrayList<String> mDataList = new ArrayList<>();
    private int mSelection = 0;

    public AdapterLeft(Context mContext, ArrayList<String> mDataList) {
        this.mContext = mContext;
        this.mDataList = mDataList;
    }

    @Override
    public int getCount() {
        if (null != mDataList)
            return mDataList.size();
        return 0;
    }

    @Override
    public Object getItem(int i) {
        if (null != mDataList)
            return mDataList.get(i);
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder = null;
        if (null == view){
            viewHolder = new ViewHolder();
            view = LayoutInflater.from(mContext).inflate(R.layout.item_left, null);
            viewHolder.textContent = (TextView) view.findViewById(R.id.text_content);
            view.setTag(viewHolder);
        }else {
            viewHolder = (ViewHolder) view.getTag();
        }
        // 設定被選中的item的字型顏色
        if (null != viewHolder.textContent && mSelection == position){
            viewHolder.textContent.setTextColor(Color.RED);
        }else {
            viewHolder.textContent.setTextColor(Color.BLACK);
        }
        if (null != viewHolder.textContent && null != mDataList && mDataList.size()>0){
            viewHolder.textContent.setText(mDataList.get(position));
        }else {
            Log.i(TAG, "getView: null == mDataList");
        }
        return view;
    }

    public int getSelection() {
        return mSelection;
    }

    public void setSelection(int selection) {
        mSelection = selection;
        notifyDataSetChanged();
    }

    class ViewHolder{
        TextView textHead;
        TextView textContent;
    }
}

  2、右邊RecyclerView介面卡AdapterRight.java,重點是RecyclerView移動到指定的位置moveToPosition(int n)方法。佈局同選單ListView中的item佈局,只有一個TextView就不貼出來了。

package com.zyf.linkagelistview.adapter;

import android.content.Context;
import android.os.SystemClock;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.zyf.linkagelistview.MainActivity;
import com.zyf.linkagelistview.R;
import com.zyf.linkagelistview.bean.Bean;

import java.util.ArrayList;

/**
 * Created by zyf on 2017/5/8.
 * 右邊RecyclerView介面卡
 */

public class AdapterRight extends RecyclerView.Adapter {

    private static final String TAG = AdapterRight.class.getSimpleName();
    private Context mContext;
    private ArrayList<Bean> mDataList = new ArrayList<>();
    private ArrayList<Integer> mTitleIntList = new ArrayList<>();
    private RecyclerView mRecyclerView;
    private boolean mShouldScroll = false;

    public AdapterRight(Context context, ArrayList<Bean> dataList, ArrayList<Integer> titleIntList, RecyclerView recyclerView) {
        mContext = context;
        mDataList = dataList;
        mTitleIntList = titleIntList;
        mRecyclerView = recyclerView;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_right, null));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ViewHolder viewHolder = (ViewHolder) holder;
        if (null != mDataList && mDataList.size() > 0) {
            viewHolder.textContent.setText(mDataList.get(position).getText());
        } else {
            Log.i(TAG, "getView: null == mDataList");
        }
    }

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

    public void setSelection(int pos) {
        if (pos < mDataList.size()) {
            moveToPosition(pos);
        }
    }

/**
 * 使RecyclerView移動到指定的位置
 */
    private void moveToPosition(final int n) {
        //先從RecyclerView的LayoutManager中獲取第一項和最後一項的Position
        int firstItem = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
        int lastItem = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
        int pos = mTitleIntList.get(n);
        mShouldScroll = false;
        mRecyclerView.setOnScrollListener(new RecyclerViewListener(n));
        //然後區分情況
        if (pos <= firstItem) {
            //當要置頂的項在當前顯示的第一個項的前面時
            mRecyclerView.scrollToPosition(pos);
        } else if (pos <= lastItem) {
            //當要置頂的項已經在螢幕上顯示時
            int top = mRecyclerView.getChildAt(pos - firstItem).getTop() - 50;
            mRecyclerView.scrollBy(0, top);
        } else {
            //當要置頂的項在當前顯示的最後一項的後面時,呼叫scrollToPosition只會將該項滑動到螢幕上。需要再次滑動到頂部
            mRecyclerView.scrollToPosition(pos);
            //這裡這個變數是用在RecyclerView滾動監聽裡面的
            mShouldScroll = true;
        }
    }

    /**
     * 滾動監聽
     */
    class RecyclerViewListener extends RecyclerView.OnScrollListener{
        private int n = 0;
        RecyclerViewListener(int n) {
            this.n = n;
        }
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //在這裡進行第二次滾動
            if (mShouldScroll ){
                mShouldScroll = false;
                moveToPosition(n);
            }
        }
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView textContent;
        ViewHolder(View itemView) {
            super(itemView);
            textContent = (TextView) itemView.findViewById(R.id.text_content);
        }
    }
}

  四、RecyclerView吸頂效果的實現所必須要的自定義ItemDecoration 類 這個類需要認真看一下,實現了吸頂效果。通過getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)方法設定預留吸頂的高度,在onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)方法以及onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)方法中進行了座標的計算,然後使用Canvas以及Paint、TextPaint繪製出吸頂

package com.zyf.linkagelistview;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;

import com.zyf.linkagelistview.bean.Bean;

import java.util.ArrayList;

/**
 * Created by zyf on 2017/5/8.
 * 實現吸頂功能的RecyclerView
 */

public class ItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = ItemDecoration.class.getSimpleName();
    private Context mContext;
    private ArrayList<Bean> mDataList = new ArrayList<>();
    private OnDecorationCallback mOnDecorationCallback;

    private Paint mPaint;
    private TextPaint mTextPaint;

    private int mTopGap = 50;          // 吸頂高(可隨意改變)
    private int mAlignBottom = 10;


    public ItemDecoration(Context context, ArrayList<Bean> dataList, OnDecorationCallback onDecorationCallback) {
        this.mContext = context;
        this.mDataList = dataList;
        this.mOnDecorationCallback = onDecorationCallback;

        Resources resources = mContext.getResources();
        mPaint = new Paint();
        mPaint.setColor(resources.getColor(R.color.black));
        mTextPaint = new TextPaint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setAntiAlias(true); // 去鋸齒
        mTextPaint.setTextSize(25);
        mTextPaint.setTextAlign(Paint.Align.LEFT);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int pos = parent.getChildAdapterPosition(view);
        String groupId = mOnDecorationCallback.onGroupId(pos);
        if (groupId.equals("-1"))
            return;
        if (pos == 0 || isGroupFirstItem(pos)){
            outRect.top = mTopGap;   // 每組的頭部都預留出位置
            if (mDataList.get(pos).getTitle().equals(""))
                outRect.top = 0;
        }else {
            outRect.top = 0;
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i=0; i<childCount; i++){
            View view = parent.getChildAt(i);
            int pos = parent.getChildAdapterPosition(view);
            String groupId = mOnDecorationCallback.onGroupId(pos);
            if (groupId.equals("-1"))
                return;
            String textLine = mOnDecorationCallback.onGroupFirstStr(pos).toUpperCase();
            if (textLine.equals("")){
                float top = view.getTop();
                float bottom = view.getTop();
                c.drawRect(left, top, right, bottom, mPaint);
                return;
            }else {
                if (pos == 0 || isGroupFirstItem(pos)){  // 當前位置為0或為一組中的第一個時,顯示頂部
                    float top = view.getTop() - mTopGap;
                    float bottom =  view.getTop();
                    c.drawRect(left, top, right, bottom, mPaint);
                    c.drawText(textLine, left, bottom, mTextPaint);
                }
            }

        }
    }

    // 在onDraw之後呼叫,此處做吸頂一直存在的功能
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        int itemCount = state.getItemCount();
        int childCount = parent.getChildCount();
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        String preGroupId = "";
        String groupId = "-1";

        for (int i=0; i<childCount; i++){
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);

            preGroupId = groupId;
            groupId = mOnDecorationCallback.onGroupId(position);

            if (groupId.equals("-1") || groupId.equals(preGroupId))
                continue;
            String textLine = mOnDecorationCallback.onGroupFirstStr(position).toUpperCase();
            if (TextUtils.isEmpty(textLine))
                continue;

            int viewBottom = view.getBottom();
            // 當view.getTop()<mTopGap的時候,一直顯示在頂部mTopGap的位置
            float textY = Math.max(mTopGap, view.getTop());

            // 此處實現被後一個title頂出螢幕的效果
            if (position + 1 < itemCount){
                String nextGroupId = mOnDecorationCallback.onGroupId(position + 1);
                // 當後面一組的頂部位置到達當前組吸頂的底部時,將當前組吸頂往上移動(被頂出螢幕)
                if (!nextGroupId.equals(groupId) && viewBottom < textY){
                    textY = viewBottom;
                }
            }

            if (view.getTop() < textY){
                mOnDecorationCallback.onGroupFirstStr(textLine);
            }
            c.drawRect(left, textY - mTopGap, right, textY, mPaint);
            c.drawText(textLine, left + 2 * mAlignBottom, textY - mAlignBottom, mTextPaint);
        }
    }

    /**
     * 判斷是否為組內的第一個item
     * @param pos
     * @return
     */
    private boolean isGroupFirstItem(int pos){
        if (pos == 0){
            return true;
        }else{
            String preGroupId = mOnDecorationCallback.onGroupId(pos - 1);
            String groupId = mOnDecorationCallback.onGroupId(pos);
            if (groupId.equals(preGroupId)){
                return false;
            }else {
                return true;
            }
        }
    }

    /**
     * 外部介面
     */
    interface OnDecorationCallback{
        String onGroupId(int pos);          // 返回pos位置對應的title
        String onGroupFirstStr(int pos);    // 返回pos位置對應的title(用於對比title)
        void onGroupFirstStr(String title); // 傳入的是title
    }
}

這樣就可以實現簡單的ListView聯動效果。

  五、總結
  該聯動Demo主要思路是點選左邊選單ListView的item,呼叫RecyclerView介面卡中移動到指定的位置moveToPosition(int n)方法,實現右邊RecyclerView滾動到指定位置;滑動右邊RecyclerView,呼叫左邊選單ListView中介面卡的setSelection(int pos)方法實現左邊聯動的功能。 以及RecyclerView的吸頂效果的實現,也是本篇部落格的重點。
  建議認真理解下RecyclerView的介面卡類AdapterRight中moveToPosition(int n)方法,以及實現吸頂功能的自定義ItemDecoration類中吸頂效果的實現方式。大神們如發現有bug或者更好的實現方式,歡迎私信交流!
                                  —————by Jeff—————-
                              —————分享使我快樂,感謝您的閱讀—————