菜单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—————-
                              —————分享使我快乐,感谢您的阅读————— 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__Yvan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值