Android仿IOS式越界回弹效果
来源:互联网 发布:自动刷弹幕软件 编辑:程序博客网 时间:2024/05/21 23:32
前言:Android自带的越界样式是滑到两端发荧光,不过就个人而言觉得这还是挺丑的,一直比较喜欢ios那种越界回弹的效果,所以现在就做了个,效果下面看图。
一、SwipeMenuRecyclerView(支持item左右滑动和列表越界回弹的效果)
1.Item左右滑动
支持RecyclerView的Vertical布局并很好地解决了item左右滑动和列表上下滑动容易误触的问题,item向右滑自带回弹效果!
使用:itemView的布局文件仿照下面即可
<?xml version="1.0" encoding="utf-8"?><com.liuzhenlin.overscroll.SmoothScrollableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <RelativeLayout android:layout_width="match_parent" android:layout_height="65dp" android:background="@drawable/default_selector_recycler_item" android:clickable="true"> <!-- child views --> <ImageView android:id="@+id/image_ssll_rl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/text_ssll_rl" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_margin="5dp" android:layout_toRightOf="@id/image_ssll_rl" android:autoLink="web" android:text="文本" android:textSize="16sp" /> </RelativeLayout> <LinearLayout android:layout_width="150dp" android:layout_height="match_parent" android:orientation="horizontal"> <Button android:id="@+id/button_top" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@color/orange" android:gravity="center" android:text="置顶" android:textColor="@android:color/white" android:textSize="16sp" /> <Button android:id="@+id/button_delete" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@color/red" android:gravity="center" android:text="删除" android:textColor="@android:color/white" android:textSize="16sp" /> <!-- and can place more buttons here... --> </LinearLayout></com.liuzhenlin.overscroll.SmoothScrollableLinearLayout>SmoothScrollableLinearLayout用以实现itemView的平滑滚动,下面为其两个主要的方法:
/** * Smoothly scroll this view to a position relative to its old position. * * @param deltaX The amount of pixels to scroll by horizontally. * Positive numbers will scroll the view to the right. * @param deltaY The amount of pixels to scroll by vertically. * Positive numbers will scroll the view up. * @param duration duration of the scroll in milliseconds. */ public void smoothScrollBy(int deltaX, int deltaY, int duration) { if (deltaX != 0 || deltaY != 0) { mOverScroller.startScroll(getScrollX(), getScrollY(), -deltaX, deltaY, duration);// setScrollX(getScrollX() - deltaX);// setScrollY(getScrollY() + deltaY); invalidate(); } } /** * Smoothly scroll this view to a position. * * @param desX The x position to scroll to in pixels. * @param desY The y position to scroll to in pixels. * @param duration duration of the scroll in milliseconds. */ public void smoothScrollTo(int desX, int desY, int duration) { if (-getScrollX() != desX || getScrollY() != desY) { final int deltaX = getScrollX() - (desX > 0 ? desX : -desX); final int deltaY = desY - getScrollY(); smoothScrollBy(deltaX, deltaY, duration); } } @Override public void computeScroll() { // 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 if (mOverScroller.computeScrollOffset()) { scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY()); invalidate(); } }
可在代码和xml文件中禁用此功能(Vertical布局下默认开启)
mSwipeMenuRecyclerView.setItemScrollingEnabled(false);
app:item_scrolling_enabled="false"
列表越界回弹完美支持RecyclerView的 Vertical布局;对Horizontal布局的RecyclerView,最右端越界回弹暂时有bug,还没找到解决办法,如果有人有办法,欢迎能指正!
可在代码或布局文件中禁用此功能以使用默认的荧光效果
mSwipeMenuRecyclerView.setOverscrollEnabled(false);
app:overscroll_enabled="false"
3.支持addHeaderView和addFooterView
实现方法借鉴自csdn的一篇帖子:Android 优雅的为RecyclerView添加HeaderView和FooterView
public static class HeaderAndFooterWrapper extends RecyclerView.Adapter { protected static final int BASE_ITEM_TYPE_HEADER = 1000_0000; protected static final int BASE_ITEM_TYPE_FOOTER = 2000_0000; protected final SparseArray<View> mHeaderViews = new SparseArray<>(); protected final SparseArray<View> mFooterViews = new SparseArray<>(); private final RecyclerView.Adapter mInnerAdapter; protected HeaderAndFooterWrapper(RecyclerView.Adapter adapter) { mInnerAdapter = adapter; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mHeaderViews.get(viewType) != null) { return new ViewHolder(mHeaderViews.get(viewType)); } else if (mFooterViews.get(viewType) != null) { return new ViewHolder(mFooterViews.get(viewType)); } return mInnerAdapter.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(int position) { if (isHeaderViewPos(position)) { return mHeaderViews.keyAt(position); } else if (isFooterViewPos(position)) { return mFooterViews.keyAt(position - getHeaderCount() - getInitialItemCount()); } return mInnerAdapter.getItemViewType(position - getHeaderCount()); } @Override public int getItemCount() { return getHeaderCount() + getFooterCount() + getInitialItemCount(); } protected int getInitialItemCount() { return mInnerAdapter.getItemCount(); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderViewPos(position)) { return; } if (isFooterViewPos(position)) { return; } mInnerAdapter.onBindViewHolder(holder, position - getHeaderCount()); } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mInnerAdapter.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { final int viewType = getItemViewType(position); if (mHeaderViews.get(viewType) != null) { return gridLayoutManager.getSpanCount(); } else if (mFooterViews.get(viewType) != null) { return gridLayoutManager.getSpanCount(); } if (spanSizeLookup != null) return spanSizeLookup.getSpanSize(position); return 1; } }); gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount()); } } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { mInnerAdapter.onViewAttachedToWindow(holder); final int position = holder.getLayoutPosition(); if (isHeaderViewPos(position) || isFooterViewPos(position)) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } } protected boolean isHeaderViewPos(int position) { return position < getHeaderCount(); } protected boolean isFooterViewPos(int position) { return position >= getHeaderCount() + getInitialItemCount(); } public void addHeaderView(View view) { mHeaderViews.put(BASE_ITEM_TYPE_HEADER + mHeaderViews.size(), view); } public void addFooterView(View view) { mFooterViews.put(BASE_ITEM_TYPE_FOOTER + mFooterViews.size(), view); } public <V extends View> V getHeaderView(int position) { return (V) mHeaderViews.valueAt(position); } public <V extends View> V getFooterView(int position) { return (V) mFooterViews.valueAt(position - getHeaderCount()); } public int getHeaderCount() { return mHeaderViews.size(); } public int getFooterCount() { return mFooterViews.size(); } } public static class ViewHolder extends RecyclerView.ViewHolder { private final SparseArray<View> mViews = new SparseArray<>(); public final View mConvertView; public ViewHolder(View itemView) { super(itemView); mConvertView = itemView; mViews.put(mConvertView.getId(), mConvertView); } /** * 通过view的id获取控件 */ public <V extends View> V findViewById(@IdRes int id) { V view = (V) mViews.get(id); if (view == null) { view = mConvertView.findViewById(id); mViews.put(id, view); } return view; } }}
默认不启用,如要使用请在布局文件中指明
app:use_header_and_footer_wrapper="true"
也可在程序中设置,但需要在每次setAdapter(adapter)之前调用才有效
/** * Must be invoked before per invoking of {@link #setAdapter(Adapter)}, * or else it will not be valid unless you invoke that method later. */public void setUsingAdapterWrapper(boolean usingAdapterWrapper) { isUsingAdapterWrapper = usingAdapterWrapper;}
注:如果使用该功能,SwipeMenuRecyclerView刷新itemView时需要这样使用
mSwipeMenuRecyclerView.getAdapterWrapper().notifyDataSetChanged();mSwipeMenuRecyclerView.getAdapterWrapper().notifyItemChanged(position);// position从第一个headerView开始计
RecyclerView的下列方法,获取的position也是从第一个headerView(如果存在)开始计mSwipeMenuRecyclerView.getChildViewHolder(itemView).getAdapterPosition();mSwipeMenuRecyclerView.getChildViewHolder(itemView).getLayoutPosition();mSwipeMenuRecyclerView.getChildLayoutPosition(itemView);mSwipeMenuRecyclerView.getChildAdapterPosition(itemView);mSwipeMenuRecyclerView.getLayoutManager().getPosition(itemView);
二、OverScrollView(越界回弹滚动view)
为了让其他布局和view(比如LinearLayout、RelativeLayout、ImageView、TextView等)实现越界回弹,所以这里搞了个 越界回弹滚动view,效果和上面类似。
使用:按照ScrollView的使用方法即可
三、HorizontalOverScrollView(水平越界回弹滚动view)
满足一般控件水平方向越界回弹的需求
使用:和HorizontalScrollView相类似
注:OverScrollView和HorizontalOverScrollView同样可禁用越界回弹的功能(和SwipeMenuRecyclerView一样),OverScrollView还可以搭配HorizontalOverScrollView一起使用,实现四个方向的越界回弹。
四、OverScrollBase接口
对于ListView、GridView等带滚动条的view,OverScrollView和HorizontalOverScrollView可能不太适用,这种情况需要继承自相应控件并实现该接口的方法,再对控件的滑动事件做相应逻辑处理即可。
package com.liuzhenlin.overscroll;import android.support.annotation.IntDef;/** * Created on 2017/12/23. <br/> * Copyright (c) 2017 刘振林.All rights reserved. * * @author 刘振林 */public interface OverscrollBase { // 在两端拉动时,每次所允许移动最大的像素点 int MAX_ABS_DELTA_DIST = 25; // px // 回弹时间 int DURATION_SPRING_BACK = 250; // ms int OVERSCROLL_EDGE_UNSPECIFIED = 0; int OVERSCROLL_EDGE_TOP = 1 << 0; int OVERSCROLL_EDGE_BOTTOM = 1 << 1; int OVERSCROLL_EDGE_LEFT = 1 << 2; int OVERSCROLL_EDGE_RIGHT = 1 << 3; @IntDef({ OVERSCROLL_EDGE_UNSPECIFIED, OVERSCROLL_EDGE_TOP, OVERSCROLL_EDGE_BOTTOM, OVERSCROLL_EDGE_TOP | OVERSCROLL_EDGE_BOTTOM, OVERSCROLL_EDGE_LEFT, OVERSCROLL_EDGE_RIGHT, OVERSCROLL_EDGE_LEFT | OVERSCROLL_EDGE_RIGHT }) @interface OverscrollEdge { } boolean isTendToScrollCurrView(); int computeOverscrollDeltaY(); int computeOverscrollDeltaX(); /** * 是否向下拉时手指向上滑动或向上拉时手指向下滑动 或 * 向右拉时手指向左滑动或向左拉时手指向右滑动 */ boolean isPushingBack(); boolean isAtTheStart(); boolean isAtTheEnd(); void startHeaderOverscrollAnim(int from, int to, int duration); void startFooterOverscrollAnim(int from, int to, int duration); /** * 回弹至初始位置 */ void smoothSpringBack();}
最后,送上整个项目的地址:Android仿IOS式越界回弹效果