Android 仿网易严选分类下拉回弹,松开切换页面NestedScrollView+RecyclerView
前段时间,公司产品要求实现仿网易严选分类上拉下滑,并且滑动一段距离有回弹阻尼效果,距离大了直接松手刷新界面。一开始研究严选的效果,感觉不是很好实现,处理回弹或者下拉刷新UI,周末在家没事的时候想起了阻尼效果,重写NestedScrollView可以实现,然后需要处理的就是滑动一段距离实现切换,获取滑动的xy坐标将这个距离传递出去,进行刷新界面,增加下出上入动画应该就能实现。周一就实现了。可能效果不是太好,不喜勿喷。。。大神有更好的实现方式希望指导下,不吝赐教希望!欢迎鞭策!
首先重写NestedScrollView,代码如下:
public class ServiceRightScrollView extends NestedScrollView { private static final int size = 4; private View inner; private float y; // private int yDown, yMove; // private boolean isIntercept; private Rect normal = new Rect(); private OnSlideListenerInterface onSlideListener; public void setOnSlideListener(OnSlideListenerInterface onSlideListener){ this.onSlideListener = onSlideListener; } public ServiceRightScrollView(Context context) { super(context); } public ServiceRightScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0); } super.onFinishInflate(); } @Override public boolean onTouchEvent(MotionEvent ev) { if (inner == null) { return super.onTouchEvent(ev); } else { commOnTouchEvent(ev); } return super.onTouchEvent(ev); } public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: y = ev.getY(); break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { // Log.i("mlguitar", y+""); animation(); if(onSlideListener != null){ onSlideListener.OnSlideListener(y); } } break; case MotionEvent.ACTION_MOVE: final float preY = y; float nowY = ev.getY(); int deltaY = (int) (preY - nowY) / size; // scrollBy(0, deltaY); y = nowY; if (isNeedMove()) { if (normal.isEmpty()) { normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); return; } int yy = inner.getTop() - deltaY; inner.layout(inner.getLeft(), yy, inner.getRight(), inner.getBottom() - deltaY); } break; default: break; } } public void animation() { TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(200); inner.startAnimation(ta); inner.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty(); } public boolean isNeedAnimation() { return !normal.isEmpty(); } public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); return scrollY == 0 || scrollY == offset; } public interface OnSlideListenerInterface{ void OnSlideListener(float coordinate); } // @Override // public boolean onInterceptTouchEvent(MotionEvent event) { // // int y = (int) event.getY(); // // switch (event.getAction()) { // case MotionEvent.ACTION_DOWN: // yDown = y; // break; // case MotionEvent.ACTION_MOVE: // yMove = y; // //上滑 // if (yMove - yDown < 0) { // isIntercept = true; // //下滑 // } else if (yMove - yDown > 0) { // isIntercept = true; // } // break; // case MotionEvent.ACTION_UP: // break; // // } // return isIntercept; // } }
以上代码,阻尼效果就实现了。
重写onTouchEvent方法,把Y坐标传递出去,根据这个坐标处理滑动刷新处理数据,通过重写的NestedScrollView定义下面的接口,在页面实现这个接口
public interface OnSlideListenerInterface{ void OnSlideListener(float coordinate); }
private OnSlideListenerInterface onSlideListener; public void setOnSlideListener(OnSlideListenerInterface onSlideListener){ this.onSlideListener = onSlideListener; }
页面实现接口回调处理数据刷新如下:
@Override public void OnSlideListener(float coordinate) { int length = mPriceList.size(); //上滑 if (coordinate > -50 && coordinate < 600) { //设置position,根据position的状态刷新 if (currentPostion == 0) { currentPostion = currentPostion + 1; } else if (currentPostion<length-1){ currentPostion++; }else if (currentPostion >=length-1) { return; } setRightAnimation(R.anim.anim_slide_out_from_bottom); leftAdapter.setPosition(currentPostion); leftAdapter.notifyDataSetChanged(); requestRightData(currentPostion); } else if (coordinate > 1300) { //下拉 if (currentPostion ==0){ return; }else { currentPostion --; } if (currentPostion >=length) { return; } setRightAnimation(R.anim.anim_slide_out_from_top); leftAdapter.setPosition(currentPostion); leftAdapter.notifyDataSetChanged(); requestRightData(currentPostion); } Log.i("currentPostion=",currentPostion+""); }
由于界面是左右列表联动的所以还需要左右两个Adatper
左侧Adapter
public class MostLeftAdapter extends RecyclerView.Adapter { private Context mContext; private List<String> mList = new ArrayList<>(); private MyItemClickListener mItemClickListener; private int mPosition; private static int TYPE_NORMAL = 101; private static int TYPE_SELECT = 102; public MostLeftAdapter(Context context) { mContext = context; mPosition = 0; } public void setList(List<String> list) { mList = list; } public List<String> getList() { return mList; } public void setOnItemClickListener(MyItemClickListener listener) { this.mItemClickListener = listener; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //根据type显示布局,选中与未选中,当然你可以自行修改 if (viewType == TYPE_SELECT) { final View view = LayoutInflater.from(mContext).inflate(R.layout.most_left_list_item, parent, false); return new ItemViewSelectHolder(view, mItemClickListener); } else { final View view = LayoutInflater.from(mContext).inflate(R.layout.most_left_list_item, parent, false); return new ItemViewHolder(view, mItemClickListener); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ItemViewHolder item = (ItemViewHolder) holder; item.mostLeftText.setText(mList.get(position)); } @Override public int getItemCount() { return mList.size(); } @Override public int getItemViewType(int position) { if (position == mPosition) { return TYPE_SELECT; } else { return TYPE_NORMAL; } } public void setPosition(int position) { mPosition = position; } public interface MyItemClickListener { void onItemClick(View view, int postion); } public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private MyItemClickListener mListener; @BindView(R.id.most_left_text) TextView mostLeftText; public ItemViewHolder(View itemView, MyItemClickListener listener) { super(itemView); this.mListener = listener; itemView.setOnClickListener(this); ButterKnife.bind(this, itemView); } @Override public void onClick(View v) { if (mListener != null) { mListener.onItemClick(v, getLayoutPosition()); } } } private class ItemViewSelectHolder extends ItemViewHolder { public ItemViewSelectHolder(View view, MyItemClickListener listener) { super(view, listener); itemView.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.most_left_select_bg)); mostLeftText.setTextColor(mContext.getResources().getColor(R.color.red_pre)); } } }
右侧Adapter:
package com.hd.hedong.myapplication; import android.content.Context; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; /** * Created by hedong on 16-11-9. */ public class MostRightAdapter extends RecyclerView.Adapter { private Context mContext; private List<MostSeriesBean> mList = new ArrayList<>(); private MyItemClickListener mItemClickListener; private RecyclerView mRecyclerView; private View VIEW_FOOTER; private View VIEW_HEADER; //Type private int TYPE_NORMAL = 1000; private int TYPE_HEADER = 1001; private int TYPE_FOOTER = 1002; public MostRightAdapter(Context context) { mContext = context; } public void setList(List<MostSeriesBean> list) { if (list != null) { mList = list; } } public void setOnItemClickListener(MyItemClickListener listener) { this.mItemClickListener = listener; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_FOOTER) { return new FooterViewHodler(VIEW_FOOTER); } else if (viewType == TYPE_HEADER) { return new FooterViewHodler(VIEW_HEADER); } else { final View view = LayoutInflater.from(mContext).inflate(R.layout.series_list_item, parent, false); return new ItemViewHolder(view, mItemClickListener); } } @Override public int getItemViewType(int position) { if (isHeaderView(position)) { return TYPE_HEADER; } else if (isFooterView(position)) { return TYPE_FOOTER; } else { return TYPE_NORMAL; } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { try { if (mRecyclerView == null && mRecyclerView != recyclerView) { mRecyclerView = recyclerView; } ifGridLayoutManager(); } catch (Exception e) { e.printStackTrace(); } } private View getLayout(int layoutId) { return LayoutInflater.from(mContext).inflate(layoutId, null); } public void addHeaderView(View headerView) { if (haveHeaderView()) { throw new IllegalStateException("hearview has already exists!"); } else { //避免出现宽度自适应 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); headerView.setLayoutParams(params); VIEW_HEADER = headerView; ifGridLayoutManager(); notifyItemInserted(0); } } public void addFooterView(View footerView) { if (haveFooterView()) { throw new IllegalStateException("footerView has already exists!"); } else { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); footerView.setLayoutParams(params); VIEW_FOOTER = footerView; ifGridLayoutManager(); notifyItemInserted(getItemCount() - 1); } } private void ifGridLayoutManager() { if (mRecyclerView == null) return; final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager.SpanSizeLookup originalSpanSizeLookup = ((GridLayoutManager) layoutManager).getSpanSizeLookup(); ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return (isHeaderView(position) || isFooterView(position)) ? ((GridLayoutManager) layoutManager).getSpanCount() : 1; } }); } } private boolean haveHeaderView() { return VIEW_HEADER != null; } public boolean haveFooterView() { return VIEW_FOOTER != null; } private boolean isHeaderView(int position) { return haveHeaderView() && position == 0; } private boolean isFooterView(int position) { return haveFooterView() && position == getItemCount() - 1; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (!isHeaderView(position) && !isFooterView(position)) { if (haveHeaderView()) position--; ItemViewHolder item = (ItemViewHolder) holder; final MostSeriesBean bean = mList.get(position); item.mostRightName.setText(bean.getName()); } } @Override public int getItemCount() { int count = (mList == null ? 0 : mList.size()); if (VIEW_FOOTER != null) { count++; } if (VIEW_HEADER != null) { count++; } return count; } public interface MyItemClickListener { void onItemClick(View view, int postion); } public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private MyItemClickListener mListener; @BindView(R.id.series_name) TextView mostRightName; public ItemViewHolder(View itemView, MyItemClickListener listener) { super(itemView); this.mListener = listener; itemView.setOnClickListener(this); ButterKnife.bind(this, itemView); } @Override public void onClick(View v) { if (mListener != null) { mListener.onItemClick(v, getLayoutPosition()); } } } public class FooterViewHodler extends RecyclerView.ViewHolder { public FooterViewHodler(View itemView) { super(itemView); } } }
由于NestedScrollView嵌套RecyclerView滑动会冲突,在Activity或者Fragment需要禁止RecyclerView滑动:
GridLayoutManager layoutManager1 = new GridLayoutManager(this,3) { @Override public boolean canScrollVertically() { return false; } };以上就是基本实现, 源代码在此