菜单ListView联动内容RecyclerView(带吸顶效果)
2017-05-09 14:34
197 查看
这两天测试发的bug修得差不多了,有点属于自己的时间,写了个仿美团/京东的ListView联动Demo,现供大家参考参考,如有bug或更好的实现方式,望大家多多指出。
先来看看效果
一、首先我们来看看MainActivity.java 这里有data和view的处理。data为测试用的,view为左边一个ListView以及适配器AdapterLeft和右边RecyclerView和适配器AdapterRight,提醒下RecyclerView别忘了setLayoutManager()。
其布局也就一个ListView和一个RecyclerView
二、数据需要的Bean
三、两个适配器
1、左边ListView适配器AdapterLeft.java,方法setSelection(int selection)设置选中其中的item。此处布局就一个TextView就不贴布局代码了。
2、右边RecyclerView适配器AdapterRight.java,重点是RecyclerView移动到指定的位置moveToPosition(int n)方法。布局同菜单ListView中的item布局,只有一个TextView就不贴出来了。
四、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绘制出吸顶
这样就可以实现简单的ListView联动效果。
五、总结
该联动Demo主要思路是点击左边菜单ListView的item,调用RecyclerView适配器中移动到指定的位置moveToPosition(int n)方法,实现右边RecyclerView滚动到指定位置;滑动右边RecyclerView,调用左边菜单ListView中适配器的setSelection(int pos)方法实现左边联动的功能。 以及RecyclerView的吸顶效果的实现,也是本篇博客的重点。
建议认真理解下RecyclerView的适配器类AdapterRight中moveToPosition(int n)方法,以及实现吸顶功能的自定义ItemDecoration类中吸顶效果的实现方式。大神们如发现有bug或者更好的实现方式,欢迎私信交流!
—————by Jeff—————-
—————分享使我快乐,感谢您的阅读—————
先来看看效果
一、首先我们来看看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 10d85 .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—————-
—————分享使我快乐,感谢您的阅读—————
相关文章推荐
- RecyclerView介绍(二)实现ListView的效果
- 支持下拉刷新和上划加载更多的自定义RecyclerView(仿XListView效果)
- RecyclerView 和 ListView 性能和效果区别
- RecyclerView做ListView的效果,Recyclerview带分隔线的使用
- RecyclerView分别展示ListView,GrideView,瀑布流效果之总结
- RecyclerView的ListView显示效果
- TwinklingRefreshLayout 支持下拉刷新和上拉加载的 RefreshLayout,自带越界回弹效果,支持 RecyclerView,AbsListView,ScrollView,We
- 一个 ScrollView 里面包含 viewpager 嵌套 listview 或 RecyclerView 极少代码实现的流畅滑动效果 处理一个两层滑动 view 的自定义布局,以最少的代码实现,
- Android-使用RecyclerView的ItemDecoration 实现炫酷的 吸顶效果
- RecyclerView做ListView的效果,Recyclerview带分隔线的使用
- Material Design recyclerview+floatingactionbutto实现联动的效果
- 效果很棒的笑脸下拉刷新:Recyclerview,ListView,Scrollview都可以使用。
- RecyclerView侧滑菜单和listview实现的通讯录侧滑
- Android studio RecyclerView实现炫酷吸顶效果
- RecyclerView--实现 ListView,GridView,瀑布流 效果
- ListView实现item的卡片效果(不使用RecyclerView+CardView)
- 使用RecyclerView实现GridView和ListView混排的效果
- Android : ViewPager+RecyclerView的联动效果
- TwinklingRefreshLayout 支持下拉刷新和上拉加载的 RefreshLayout,自带越界回弹效果,支持 RecyclerView,AbsListView,ScrollView等
- RecyclerView实现ListView和GridView的效果