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"


2.越界回弹 

列表越界回弹完美支持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式越界回弹效果

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 闵行房地产 闵行 别墅区 闵行敬老院 闵行晚托班 闵行区 医院 闵行度假村 闵行 花店 闵行水仙苑 闵行 牙科 闵行经济城 闵行区中心医院 上海闵行区邮编 闵行中心医院 上海市闵行区邮政编码 上海闵行区房价 上海闵行邮编 闵行体育公园 闵行区游泳池 上海闵行区顾村镇 闵行在上海算什么水平 闵行区中医医院 闵行中心医院门诊时间 上海闵行区虹桥镇 上海市闵行区邮编 上海市闵行区房价 上海闵行招聘 上海闵行区驾校 上海闵行制冰厂 闵行开发区驾校 上海闵行驾校哪个好 上海闵行哪个驾校好 闵行区送水电话 闵行搬场电话 上海闵行驾校价格 闵行哪里租房子便宜 上海闵行房价 闵行新娘跟妆培训 上海闵行开发区 闵行区医院有哪些 闵行仓库出租 闵行家庭保洁