精华内容
下载资源
问答
  • ReactNative自定义控件之 RefreshLayout1 自定义下拉刷新控件 //自定义的下拉刷新控件 public class PullToRefreshView extends ViewGroup { ... public PullToRefreshView(Context context) { ... } public ...

    ReactNative自定义控件之 RefreshLayout

    1 自定义下拉刷新控件

    
        //自定义的下拉刷新控件
    public class PullToRefreshView extends ViewGroup {
        ...
    
        public PullToRefreshView(Context context) {
            ...
        }  
    
        public void setRefreshing(boolean refreshing) {
            ...
        }
    
        public void setOnRefreshListener(OnRefreshListener listener) {
            ...
        }
    }
    

    2 创建 ViewManager 的实现类

    官方文档中给我们的示例是创建 SimpleViewManager 的实现类,但此处的下拉刷新控件是个 ViewGroup ,所以此处实现类应继承 ViewManager 的另一个子类 ViewGroupManager

    public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{
        @Override
        public String getName() {
            return "PtrLayout";
        }
    
        @Override
        protected PullToRefreshView createViewInstance(ThemedReactContext reactContext) {
            return new PullToRefreshView(reactContext);
        }
        ...
    }

    3 给 ViewManager 添加事件监听

    但我们这是一个下拉刷新控件,有一个问题是我们如何将下拉刷新的监听事件传递给 JavaScript 呢?官方文档中写的并不清晰,还是翻阅源码吧,果不其然在源码中寻找到了我们想要的答案。

    覆写 addEventEmitters 函数将事件监听传递给 JavaScript 。

    
        public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{
        ...
        @Override
        protected void addEventEmitters(ThemedReactContext reactContext, PullToRefreshView view) {
            view.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
                            .dispatchEvent(new PtrRefreshEvent(view.getId()));
                }
            });
        }
    
        @Nullable
        @Override
        public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
            return MapBuilder.<String, Object>builder()
                    .put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))
                    .build();
        }
        ...
    }
    
    //我们将事件封装为 PtrRefreshEvent 。
    
    public class PtrRefreshEvent extends Event<PtrRefreshEvent>{
    
        protected PtrRefreshEvent(int viewTag) {
            super(viewTag);
        }
    
        @Override
        public String getEventName() {
            return "topRefresh";
        }
    
        @Override
        public void dispatch(RCTEventEmitter rctEventEmitter) {
            rctEventEmitter.receiveEvent(getViewTag(),getEventName(),null);
        }
    }
    
    细心地你肯定发现了 getExportedCustomDirectEventTypeConstants 这个函数,这里先说明一下,覆写该函数,将 topRefresh 这个事件名在 JavaScript 端映射到 onRefresh 回调属性上,这部分我们后面会在结合 JavaScript 再解释下用法。
    
    关于组件这部分大家可以参看 React Native 的 Android 部分的代码。
    
    
    

    4 使用@ReactProp 注解导出属性的设置方法

    这部分内容官方文档的介绍足够使用了,这里不再细说。

    public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{
        ...
        @ReactProp(name = "refreshing")
        public void setRefreshing(PullToRefreshView view, boolean refreshing) {
            view.setRefreshing(refreshing);
        }
    }

    5 将 ViewManager 注册到应用

    如果你熟悉 Android 的 React Native 集成的话,你只需要将 SwipeRefreshViewManager 添加到 ReactPackage 中即可

    public class MainPackage implements ReactPackage {
        ...
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
            return Arrays.asList(new SwipeRefreshViewManager());
        }
        ...
    }

    6 实现下拉刷新组件

    还记得吗,在 Android 我们通过 SwipeRefreshViewManager 中 getName 返回的控件名称,将会在这里用于引用这个原生控件。

    'use strict';
    import React, {Component, PropTypes} from 'react';
    import {View, requireNativeComponent} from 'react-native';
    import NativeMethodsMixin from 'react/lib/NativeMethodsMixin';
    import mixin from 'react-mixin';
    //引用原生下拉刷新控件
    const NativePtrView = requireNativeComponent('PtrLayout', PtrView);
    //封装一个react组件,该组件中引用了原生控件的实现
    class PtrView extends Component {
        static propTypes = {
            ...View.propTypes,
            onRefresh: PropTypes.func,
            refreshing: PropTypes.bool.isRequired
        };
    
        _nativeRef = (null: ?PtrView);
        _lastNativeRefreshing = false;
    
        constructor(props) {
            super(props);
        }
    
        componentDidMount() {
            this._lastNativeRefreshing = this.props.refreshing;
        }
    
        componentDidUpdate(prevProps = {refreshing: false}) {
            if (this.props.refreshing !== prevProps.refreshing) {
                this._lastNativeRefreshing = this.props.refreshing;
            } else if (this.props.refreshing !== this._lastNativeRefreshing) {
                this._nativeRef.setNativeProps({refreshing: this.props.refreshing});
                this._lastNativeRefreshing = this.props.refreshing;
            }
        }
        //渲染原生下拉刷新控件,这里onRefresh就是在ViewManager::getExportedCustomDirectEventTypeConstants 
        //这个函数中 topRefresh 的映射属性。 
        render() {
            return (
                <NativePtrView
                    {...this.props}
                    ref={ref => this._nativeRef = ref}
                    onRefresh={this._onRefresh.bind(this)}/>
            )
        }
    
        _onRefresh() {
            this._lastNativeRefreshing = true;
            this.props.onRefresh && this.props.onRefresh();
            this.forceUpdate();
        }
    }
    mixin.onClass(PtrView, NativeMethodsMixin);
    
    export {PtrView};

    7 下拉刷新组件的使用

    说到使用就太简单了,虽然简单但仍然要说,我们知道官方提供的组件例如 ListView 中通过 refreshControl 来指定刷新控制器,用法是这样的:

    class Demo1 extends Component {
        ...
        render() {
            return (
                <View style={{flex: 1}}>
                    <ListView 
                        ...
                        refreshControl={
                            <RefreshControl
                                refreshing={this.state.refreshing}
                                onRefresh={this._refresh.bind(this)} />
                        }
                    />
                </View>
            )
        }
    }

    8 我就在想既然可以通过 refreshControl 来指定刷新控制器,那我自定义的下拉刷新组件是不是也可以通过 refreshControl 来指定呢?带着这样的疑问,我仔细读了读 ListView/ScrollView 的源码,发现这个猜想还是蛮靠谱的,也赞叹 Facebook 的工程师们的妙笔生花。

    const ScrollView = React.createClass({
        let ScrollViewClass;
        if (Platform.OS === 'ios') {
          ScrollViewClass = RCTScrollView;
        } else if (Platform.OS === 'android') {
          if (this.props.horizontal) {
            ScrollViewClass = AndroidHorizontalScrollView;
          } else {
            ScrollViewClass = AndroidScrollView;
          }
        }
        ...
    
        const refreshControl = this.props.refreshControl;
        if (refreshControl) {
          if (Platform.OS === 'ios') {
            ...
          } else if (Platform.OS === 'android') {
            // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
            // Since the ScrollView is wrapped add the style props to the
            // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
            // 此处就是重点,通过 cloneElement 创建一个新的 ReactElement,而 refreshControl 是通过 props 指定而来并没有写死,Good!
            return React.cloneElement(
              refreshControl,
              {style: props.style},
              <ScrollViewClass {...props} ref={this._setScrollViewRef}>
                {contentContainer}
              </ScrollViewClass>
            );
          }
        }
        return (
          ...
        );
    })

    9 基于以上的分析以及我们对于属性的封装,我们的写法也相当的原味:

    class Demo2 extends Component {
        ...
        render() {
            return (
                <View style={{flex: 1}}>
                    <ListView 
                        ...
                        refreshControl={
                             //这里为了保证只在Android平台上使用该组件,如果iOS端也有原生控件的实现,
                             //那就不必考虑平台了。
                             Platform.OS === 'android' ? 
                             <PtrView
                                  refreshing={this.state.refreshing}
                                  onRefresh={this._refresh.bind(this)} />
                             :
                             <RefreshControl
                                  refreshing={this.state.refreshing}
                                  onRefresh={this._refresh.bind(this)} />
                        }
                    />
                </View>
            )
        }
    }
    展开全文
  • 1.自定义控件通用接口封装 2.安卓手势分发 GestureDetector 拦截处理 3.自定义控件之onLayout布局 4.借助Scroller实现视图的自动滚动 3.整个控件基础类图结构 4.主要代码 1.首先定义 HiRefreshLayout 的 通用接口 ...

    在这里插入图片描述

    1.案例演示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v01OFECJ-1633934671223)(../../pic/QQ%E5%BD%95%E5%B1%8F20211011111702.gif)]

    2.涉及到的知识点

    1.自定义控件通用接口封装

    2.安卓手势分发 GestureDetector 拦截处理

    3.自定义控件之onLayout布局

    4.借助Scroller实现视图的自动滚动

    3.整个控件基础类图结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKyR1f18-1633934671227)(../../pic/refresh.png)]

    4.主要代码

    1.首先定义 HiRefreshLayout 的 通用接口

    下拉刷新控件:

    1.可设置是否下拉刷新时禁止滚动

    2.刷新完成时状态更新,及派发相应的监听事件

    3.提供给使用者设置监听器以获取控件的不同状态回调

    4.使用者可自行设置 刷新要显示的视图,只要继承自 HiOverView 抽象类即可

    public interface HiRefresh {
    
        /**
         * 刷新时是否禁止滚动
         *
         * @param disableRefreshScroll 否禁止滚动
         */
        void setDisableRefreshScroll(boolean disableRefreshScroll);
    
        /**
         * 刷新完成
         */
        void refreshFinished();
    
        /**
         * 设置下拉刷新的监听器
         *
         * @param hiRefreshListener 刷新的监听器
         */
        void setRefreshListener(HiRefresh.HiRefreshListener hiRefreshListener);
    
        /**
         * 设置下拉刷新的视图
         *
         * @param hiOverView 下拉刷新的视图
         */
        void setRefreshOverView(HiOverView hiOverView);
    
        interface HiRefreshListener {
    
            void onRefresh();
    
            boolean enableRefresh();
        }
    }
    

    2.Header 控件的基本属性

    下拉过程中 Header 的不同状态,更新状态和视图显示。

    松开手后,根据当前 Header 的状态来做相应的逻辑处理

    /**
     * 下拉刷新的Overlay视图,可以重载这个类来定义自己的Overlay
     */
    public abstract class HiOverView extends FrameLayout {
        public enum HiRefreshState {
            /**
             * 初始态
             */
            STATE_INIT,
            /**
             * Header展示的状态
             */
            STATE_VISIBLE,
            /**
             * 超出可刷新距离的状态
             */
            STATE_OVER,
            /**
             * 刷新中的状态
             */
            STATE_REFRESH,
            /**
             * 超出刷新位置松开手后的状态
             */
            STATE_OVER_RELEASE
        }
    
        protected HiRefreshState mState = HiRefreshState.STATE_INIT;
        /**
         * 触发下拉刷新 需要的最小高度
         */
        public int mPullRefreshHeight;
        /**
         * 最小阻尼
         */
        public float minDamp = 1.6f;
        /**
         * 最大阻尼
         */
        public float maxDamp = 2.2f;
    
        public HiOverView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            preInit();
        }
    
        public HiOverView(Context context, AttributeSet attrs) {
            super(context, attrs);
            preInit();
        }
    
        public HiOverView(Context context) {
            super(context);
            preInit();
        }
    
        protected void preInit() {
            mPullRefreshHeight = HiDisplayUtil.dp2px(66, getResources());
            init();
        }
    
        /**
         * 初始化
         */
        public abstract void init();
    
        protected abstract void onScroll(int scrollY, int pullRefreshHeight);
    
        /**
         * 显示Overlay
         */
        protected abstract void onVisible();
    
        /**
         * 超过Overlay,释放就会加载
         */
        public abstract void onOver();
    
        /**
         * 开始加载
         */
        public abstract void onRefresh();
    
        /**
         * 加载完成
         */
        public abstract void onFinish();
    
        /**
         * 设置状态
         *
         * @param state 状态
         */
        public void setState(HiRefreshState state) {
            mState = state;
        }
    
        /**
         * 获取状态
         *
         * @return 状态
         */
        public HiRefreshState getState() {
            return mState;
        }
    
    }
    

    3.下拉刷新控件

    /**
     * author : shengping.tian
     * time   : 2021/07/30
     * desc   : 下拉刷新View
     * version: 1.0
     */
    public class HiRefreshLayout extends FrameLayout implements HiRefresh {
    
        private static final String TAG = HiRefreshLayout.class.getSimpleName();
        //当前刷新状态
        private HiOverView.HiRefreshState mState;
        //手势监听
        private GestureDetector mGestureDetector;
        //自动滚动
        private AutoScroller mAutoScroller;
        //刷新结果回调
        private HiRefresh.HiRefreshListener mHiRefreshListener;
        //刷新显示的布局
        protected HiOverView mHiOverView;
    
        private int mLastY;
    
        //刷新时是否禁止滚动
        private boolean disableRefreshScroll;
    
        public HiRefreshLayout(@NonNull Context context) {
            this(context, null);
        }
    
        public HiRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HiRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            mGestureDetector = new GestureDetector(getContext(), hiGestureDetector);
            mAutoScroller = new AutoScroller();
        }
    
    
        @Override
        public void setDisableRefreshScroll(boolean disableRefreshScroll) {
            this.disableRefreshScroll = disableRefreshScroll;
        }
    
        @Override
        public void refreshFinished() {
            final View head = getChildAt(0);
            Log.i(TAG, "refreshFinished head-bottom:" + head.getBottom());
            mHiOverView.onFinish();
            mHiOverView.setState(HiOverView.HiRefreshState.STATE_INIT);
            final int bottom = head.getBottom();
            if (bottom > 0) {
                //下over pull 200,height 100
                //  bottom  =100 ,height 100
                recover(bottom);
            }
            mState = HiOverView.HiRefreshState.STATE_INIT;
        }
    
        @Override
        public void setRefreshListener(HiRefreshListener hiRefreshListener) {
            mHiRefreshListener = hiRefreshListener;
        }
    
        /**
         * 设置下拉刷新的视图
         *
         * @param hiOverView
         */
        @Override
        public void setRefreshOverView(HiOverView hiOverView) {
            if (this.mHiOverView != null) {
                removeView(mHiOverView);
            }
            this.mHiOverView = hiOverView;
            LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            addView(mHiOverView, 0, params);
        }
    
    
        /**
         * 手指往下滑动, distanceY 为负数
         */
        HiGestureDetector hiGestureDetector = new HiGestureDetector() {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Log.e(TAG, "onScroll distanceY:" + distanceY);
                if (Math.abs(distanceX) > Math.abs(distanceY) || mHiRefreshListener != null && !mHiRefreshListener.enableRefresh()) {
                    //横向滑动距离大于纵向滑动距离  不处理,或者设置不支持下拉刷新 enableRefresh,则该事件统一不处理,返回 false
                    return false;
                }
                if (disableRefreshScroll && mState == HiOverView.HiRefreshState.STATE_REFRESH) {
                    //刷新时候是否禁止滑动,并且当前状态是刷新状态,拦截事件
                    return true;
                }
                View head = getChildAt(0);
                //找到第一个可以滑动的View
                View child = HiScrollUtil.findScrollableChild(HiRefreshLayout.this);
                if (HiScrollUtil.childScrolled(child)) {
                    //如果列表发生了滚动则不处理
                    return false;
                }
                //没有刷新或没有达到可以刷新的距离,且头部已经划出或下拉
                if ((mState != HiOverView.HiRefreshState.STATE_REFRESH || head.getBottom() <= mHiOverView.mPullRefreshHeight) && (head.getBottom() > 0 || distanceY <= 0.0F)) {
                    //还在滑动中
                    if (mState != HiOverView.HiRefreshState.STATE_OVER_RELEASE) {
                        int speed;
                        //阻尼计算
                        if (child.getTop() < mHiOverView.mPullRefreshHeight) {
                            speed = (int) (mLastY / mHiOverView.minDamp);
                        } else {
                            speed = (int) (mLastY / mHiOverView.maxDamp);
                        }
                        //如果是正在刷新状态,则不允许在滑动的时候改变状态
                        boolean bool = moveDown(speed, true);
                        mLastY = (int) (-distanceY);
                        return bool;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        };
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            //事件分发处理
            if (!mAutoScroller.isFinished()) {
                return false;
            }
            View head = getChildAt(0);
            if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL
                    || ev.getAction() == MotionEvent.ACTION_POINTER_INDEX_MASK) {//松开手
                if (head.getBottom() > 0) {
                    if (mState != HiOverView.HiRefreshState.STATE_REFRESH) {//正在刷新
                        recover(head.getBottom());
                        return false;
                    }
                }
                mLastY = 0;
            }
            boolean consumed = mGestureDetector.onTouchEvent(ev);
            Log.i(TAG, "gesture consumed:" + consumed);
            if ((consumed || (mState != HiOverView.HiRefreshState.STATE_INIT && mState != HiOverView.HiRefreshState.STATE_REFRESH)) && head.getBottom() != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);//让父类接受不到真实的事件
                return super.dispatchTouchEvent(ev);
            }
            if (consumed) {
                return true;
            } else {
                return super.dispatchTouchEvent(ev);
            }
        }
    
    
        /**
         * 定义 Head的位置
         */
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            //定义head和child的排列位置
            View head = getChildAt(0);
            View child = getChildAt(1);
            if (head != null && child != null) {
                Log.i(TAG, "onLayout head-height:" + head.getMeasuredHeight());
                int childTop = child.getTop();
                if (mState == HiOverView.HiRefreshState.STATE_REFRESH) {
                    head.layout(0, mHiOverView.mPullRefreshHeight - head.getMeasuredHeight(), right, mHiOverView.mPullRefreshHeight);
                    child.layout(0, mHiOverView.mPullRefreshHeight, right, mHiOverView.mPullRefreshHeight + child.getMeasuredHeight());
                } else {
                    //left,top,right,bottom
                    head.layout(0, childTop - head.getMeasuredHeight(), right, childTop);
                    child.layout(0, childTop, right, childTop + child.getMeasuredHeight());
                }
                View other;
                //让HiRefreshLayout节点下两个以上的child能够不跟随手势移动以实现一些特殊效果,如悬浮的效果
                for (int i = 2; i < getChildCount(); ++i) {
                    other = getChildAt(i);
                    other.layout(0, top, right, bottom);
                }
                Log.i(TAG, "onLayout head-bottom:" + head.getBottom());
            }
        }
    
    
        private void recover(int dis) {//dis =200  200-100
            if (mHiRefreshListener != null && dis > mHiOverView.mPullRefreshHeight) {
                mAutoScroller.recover(dis - mHiOverView.mPullRefreshHeight);
                mState = HiOverView.HiRefreshState.STATE_OVER_RELEASE;
            } else {
                mAutoScroller.recover(dis);
            }
        }
    
        /**
         * 根据偏移量移动header与child
         *
         * @param offsetY 偏移量
         * @param nonAuto 是否非自动滚动触发
         */
        private boolean moveDown(int offsetY, boolean nonAuto) {
            Log.i(TAG,"moveDown nonAuto  = " + nonAuto);
            //todo 需要添加默认的 Header,以及解决界面空布局出现下拉刷新异常
            View head = getChildAt(0);
            View child = getChildAt(1);
            int childTop = child.getTop() + offsetY;
            Log.i(TAG, "moveDown head-bottom:" + head.getBottom() + ",child.getTop():" + child.getTop() + ",offsetY:" + offsetY);
            if (childTop <= 0) {
                offsetY = -child.getTop();
                //移动head与child的位置,到原始位置
                head.offsetTopAndBottom(offsetY);
                child.offsetTopAndBottom(offsetY);
                if (mState != HiOverView.HiRefreshState.STATE_REFRESH) {
                    mState = HiOverView.HiRefreshState.STATE_INIT;
                }
            } else if (mState == HiOverView.HiRefreshState.STATE_REFRESH && childTop > mHiOverView.mPullRefreshHeight) {
                //如果正在下拉刷新中,禁止继续下拉
                return false;
            } else if (childTop <= mHiOverView.mPullRefreshHeight) {//还没超出设定的刷新距离
                if (mHiOverView.getState() != HiOverView.HiRefreshState.STATE_VISIBLE && nonAuto) {//头部开始显示
                    mHiOverView.onVisible();
                    mHiOverView.setState(HiOverView.HiRefreshState.STATE_VISIBLE);
                    mState = HiOverView.HiRefreshState.STATE_VISIBLE;
                }
                head.offsetTopAndBottom(offsetY);
                child.offsetTopAndBottom(offsetY);
                if (childTop == mHiOverView.mPullRefreshHeight && mState == HiOverView.HiRefreshState.STATE_OVER_RELEASE) {
                    Log.i(TAG, "refresh,childTop:" + childTop);
                    refresh();
                }
            } else {
                if (mHiOverView.getState() != HiOverView.HiRefreshState.STATE_OVER && nonAuto) {
                    //超出刷新位置
                    mHiOverView.onOver();
                    mHiOverView.setState(HiOverView.HiRefreshState.STATE_OVER);
                }
                head.offsetTopAndBottom(offsetY);
                child.offsetTopAndBottom(offsetY);
            }
            if (mHiOverView != null) {
                mHiOverView.onScroll(head.getBottom(), mHiOverView.mPullRefreshHeight);
            }
            return true;
        }
    
        /**
         * 刷新
         */
        private void refresh() {
            if (mHiRefreshListener != null) {
                mState = HiOverView.HiRefreshState.STATE_REFRESH;
                mHiOverView.onRefresh();
                mHiOverView.setState(HiOverView.HiRefreshState.STATE_REFRESH);
                mHiRefreshListener.onRefresh();
            }
        }
    
        /**
         * 借助Scroller实现视图的自动滚动
         * https://juejin.im/post/5c7f4f0351882562ed516ab6
         */
        private class AutoScroller implements Runnable {
    
            private Scroller mScroller;
            private int mLastY;
            private boolean mIsFinished;
    
            AutoScroller() {
                mScroller = new Scroller(getContext(), new LinearInterpolator());
                mIsFinished = true;
            }
    
            @Override
            public void run() {
                //当您想知道新位置时调用此方法。 如果返回 true,则动画尚未完成
                if (mScroller.computeScrollOffset()) {
                    moveDown(mLastY - mScroller.getCurrY(), false);
                    mLastY = mScroller.getCurrY();
                    post(this);
                } else {
                    //移除回调
                    removeCallbacks(this);
                    mIsFinished = true;
                }
            }
    
            /**
             * y方向滚动
             *
             * @param dis 距离
             */
            void recover(int dis) {
                if (dis <= 0) return;
                removeCallbacks(this);
                mLastY = 0;
                mIsFinished = false;
                mScroller.startScroll(0, 0, 0, dis, 300);
                post(this);
            }
    
            boolean isFinished() {
                return mIsFinished;
            }
        }
    }
    
    

    4.自定义 HiOverView 头部视图

    1.定义个显示文本样式的头部

    public class HiTextOverView extends HiOverView{
        private TextView mText;
        private View mRotateView;
    
        public HiTextOverView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public HiTextOverView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public HiTextOverView(Context context) {
            super(context);
        }
    
        @Override
        public void init() {
            LayoutInflater.from(getContext()).inflate(R.layout.hi_refresh_overview, this, true);
            mText = findViewById(R.id.text);
            mRotateView = findViewById(R.id.iv_rotate);
        }
    
        @Override
        protected void onScroll(int scrollY, int pullRefreshHeight) {
        }
    
        @Override
        public void onVisible() {
            mText.setText("下拉刷新");
        }
    
        @Override
        public void onOver() {
            mText.setText("松开刷新");
        }
    
        @Override
        public void onRefresh() {
            mText.setText("正在刷新...");
            Animation operatingAnim = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_anim);
            LinearInterpolator lin = new LinearInterpolator();
            operatingAnim.setInterpolator(lin);
            mRotateView.startAnimation(operatingAnim);
        }
    
        @Override
        public void onFinish() {
            mRotateView.clearAnimation();
        }
    
    }
    

    布局视图

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <LinearLayout
            android:id="@+id/refresh_area"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:gravity="center"
            android:orientation="vertical">
    
            <ImageView
                android:id="@+id/iv_rotate"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="10dp"
                android:src="@drawable/rotate_daisy" />
    
            <TextView
                android:id="@+id/text"
                android:visibility="visible"
                android:layout_marginBottom="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
        </LinearLayout>
    
    </merge>
    

    2.自定义动画 Header

    package com.tsp.android.test.refresh;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import com.airbnb.lottie.LottieAnimationView;
    import com.tsp.android.hiui.refresh.HiOverView;
    import com.tsp.android.jgs.R;
    
    
    public class HiLottieOverView extends HiOverView {
    
        private LottieAnimationView pullAnimationView;
    
        public HiLottieOverView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public HiLottieOverView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public HiLottieOverView(Context context) {
            super(context);
        }
    
        @Override
        public void init() {
            LayoutInflater.from(getContext()).inflate(R.layout.lottie_overview, this, true);
            pullAnimationView = findViewById(R.id.pull_animation);
            pullAnimationView.setAnimation("loading_wave.json");
        }
    
        @Override
        protected void onScroll(int scrollY, int pullRefreshHeight) {
        }
    
        @Override
        public void onVisible() {
        }
    
        @Override
        public void onOver() {
        }
    
        @Override
        public void onRefresh() {
            pullAnimationView.setSpeed(2);
            pullAnimationView.playAnimation();
        }
    
        @Override
        public void onFinish() {
            pullAnimationView.setProgress(0f);
            pullAnimationView.cancelAnimation();
        }
    
    }
    

    xml布局:

    <?xml version="1.0" encoding="UTF-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/refresh_overView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:id="@+id/refresh_area"
            android:layout_width="match_parent"
            android:layout_height="66dp"
            android:layout_gravity="bottom"
            android:gravity="bottom|center_horizontal"
            android:orientation="vertical">
    
            <com.airbnb.lottie.LottieAnimationView
                android:id="@+id/pull_animation"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                app:lottie_autoPlay="false"
                app:lottie_loop="true"/>
    
    
        </LinearLayout>
    </merge>
    

    注意 LottieAnimationView 需要引入 implementation “com.airbnb.android:lottie:3.3.0” 库

    5.具体使用

    1.在布局中引入下拉刷新视图

    <?xml version="1.0" encoding="utf-8"?>
    <com.tsp.android.hiui.refresh.HiRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/hiRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.tsp.android.test.refresh.RefreshDemoActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </com.tsp.android.hiui.refresh.HiRefreshLayout>
    

    2.Activity中加载视图

    class RefreshDemoActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_refresh_demo)
    
            val refreshLayout = findViewById<HiRefreshLayout>(R.id.hiRefresh)
            val xOverView = HiTextOverView(this)
    
    //        val lottieOverView = HiLottieOverView(this)
    
            refreshLayout.setRefreshOverView(xOverView)
            refreshLayout.setRefreshListener(object : HiRefresh.HiRefreshListener {
                override fun onRefresh() {
                    Handler(Looper.getMainLooper()).postDelayed({
                        refreshLayout.refreshFinished()
                    }, 1000)
                }
    
                override fun enableRefresh(): Boolean {
                    return true
                }
            })
            refreshLayout.setDisableRefreshScroll(false)
    
            initView()
        }
    
        private fun initView() {
            val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
            val data = arrayOf("RefreshItem", "RefreshItem", "RefreshItem", "RefreshItem", "RefreshItem", "RefreshItem", "RefreshItem")
            val layoutManager = LinearLayoutManager(this)
            recyclerView.layoutManager = layoutManager
            val mAdapter = MyAdapter(data)
            recyclerView.adapter = mAdapter
        }
    
    
        class MyAdapter(private val mDataset: Array<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    
            class MyViewHolder(v: View) : RecyclerView.ViewHolder(v) {
                // each data item is just a string in this case
                var textView: TextView
    
                init {
                    textView = v.findViewById(R.id.tv_title)
                }
            }
    
            override fun onCreateViewHolder(
                    parent: ViewGroup,
                    viewType: Int
            ): MyViewHolder {
                val v = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_layout, parent, false)
                return MyViewHolder(v)
            }
    
            override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
                holder.textView.text = mDataset[position]
                holder.itemView.setOnClickListener {
                    Log.d("MyAdapter","position:$position")
                }
            }
    
            override fun getItemCount(): Int {
                return mDataset.size
            }
    
        }
    }
    

    在这里插入图片描述

    5.完整代码地址

    展开全文
  • //自定义RefreshLayout 类 public class RefreshLayout extends SwipeRefreshLayout implements AbsListView.OnScrollListener { private int mTouchSlop; //滑动到最下面时的上拉操作 private ListView mListView...
    //自定义RefreshLayout 类
    public class RefreshLayout extends SwipeRefreshLayout implements AbsListView.OnScrollListener {
        private int mTouchSlop; //滑动到最下面时的上拉操作
        private ListView mListView;
        private OnLoadListener mOnLoadListener; //上拉加载监听
        private View mListViewFooter; //底部加载时的布局
        private int mYDown; //按下时的y坐标
        private int mLastY; //抬起时的y坐标
        private boolean isLoading; //是否正在加载
    
    
        public RefreshLayout(Context context) {
            this(context, null);
        }
    
        public RefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            //表示触发事件的最小距离
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
            mListViewFooter = LayoutInflater.from(context).inflate(R.layout.listview_footer, null, false);
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right,
                                int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            //初始化listview对象
            if (mListView == null) {
                int childs = getChildCount();
                if (childs > 0) {
                    View childView = getChildAt(0);
                    if (childView instanceof ListView) {
                        mListView = (ListView) childView;
                        mListView.setOnScrollListener(this);
                    }
                }
            }
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final int action = ev.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mYDown = (int) ev.getRawY();
                    break;
                case MotionEvent.ACTION_UP:
                    if (canLoad())
                        loadData();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mLastY = (int) ev.getRawY();
                    break;
                default:
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        //判断是否可以加载更多
        private boolean canLoad() {
            return isBottom() && !isLoading && isPullUp();
        }
        //判断是否到达了底部
        private boolean isBottom() {
            if (mListView != null && mListView.getAdapter() != null) {
                return mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
            }
            return false;
        }
        //判断是否是上拉操作
        private boolean isPullUp() {
            return (mYDown - mLastY) >= mTouchSlop;
        }
    
        //加载操作
        private void loadData() {
            if (mOnLoadListener != null) {
                setLoading(true);
                mOnLoadListener.onLoad();
            }
        }
        public void setLoading(boolean loading) {
            isLoading = loading;
            if (isLoading) {
                mListView.addFooterView(mListViewFooter);
            }
            else {
                mListView.removeFooterView(mListViewFooter);
                mYDown = 0;
                mLastY = 0;
            }
        }
    
        public void setOnLoadListener(OnLoadListener loadListener) {
            mOnLoadListener = loadListener;
        }
    
        @Override
        public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
    
        }
    
        @Override
        public void onScrollStateChanged(AbsListView arg0, int arg1) {
            //滚动到底部也可以加载更多
            if (canLoad()) {
                loadData();
            }
        }
    
        //加载更多的监听器
        public static interface OnLoadListener {
            public void onLoad();
        }
    }

    在Activity中调用

    private RefreshLayout swip;

     swip = (RefreshLayout) findViewById(R.id.swip_index);
            //设置滚动颜色,没有效果
            swip.setColorSchemeColors(getResources().getColor(R.color.colorAccent), getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark));
            btn_newMisson.setOnClickListener(this);
    
            //下拉刷新
            swip.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    swip.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(mContext, "新数据加载完了", Toast.LENGTH_SHORT).show();
                            //关闭动画
                            swip.setRefreshing(false);
                        }
                    }, 3000);
                }
            });
    
            //上划加载
            swip.setOnLoadListener(new RefreshLayout.OnLoadListener() {
                @Override
                public void onLoad() {
                    swip.postDelayed(new Runnable() {
    
                        @Override
                        public void run() {
                            //假数据测试
                            for (int i = 0; i < 15; i++) {
                                MissionBean bean = new MissionBean();
                                bean.sn = "100" + i;
                                bean.job = "工作内容工作内容工作内容工作内容工作内容工作内容工作内容工作内容工作内容工作内容" + i;
                                bean.dept = "第" + i + "小组";
                                bean.isend = "0";
                                list.add(bean);
    
                            }
                            adapter = new MissionlistAdapter(mContext, list);
                            lv_mission.setAdapter(adapter);
                            adapter.notifyDataSetChanged();
                            swip.setLoading(false);//关闭动画
                        }
                    }, 3000);
                }
            });
    
    展开全文
  • 2、xml: 这里项目需求不需要刷新,就设置为1,设置为0会无效的 加载更多,高度30dp,隐藏和显示,框架都设置好了,只需要在布局中设置样式即可, 2、 3、在main中: RefreshLayout refreshLayout = (RefreshLayout)...

    效果:

    在这里插入图片描述在这里插入图片描述

    1、依赖:

    
      implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-23'
      compile 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0-alpha-23'//没有使用特殊Header,可以不加这行
      implementation 'com.android.support:recyclerview-v7:28.0.0'//recycleview依赖
    
    

    2、xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <com.scwang.smartrefresh.layout.SmartRefreshLayout
            android:id="@+id/url_imgs_refreshlayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    		这里项目需求不需要刷新,就设置为1,设置为0会无效的
            <TextView
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_1"
                />
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/url_imgs_recycleview"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
    		加载更多,高度30dp,隐藏和显示,框架都设置好了,只需要在布局中设置样式即可,
            <TextView
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_30"
                android:gravity="center"
                android:text="@string/loading"
                android:textSize="@dimen/sp_14"/>
        </com.scwang.smartrefresh.layout.SmartRefreshLayout>
    
    </LinearLayout>
    
    2、
    
    <com.scwang.smartrefresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/refreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#444444"
        app:srlPrimaryColor="#444444"
        app:srlAccentColor="@android:color/white"
        app:srlEnablePreviewInEditMode="true">
        <!--background s Footer 的背景色-->
        <!--srlAccentColor srlPrimaryColor 将会改变 Header 背景色-->
        <!--srlEnablePreviewInEditMode 可以开启和关闭预览功能-->
       
           <!--ClassicsHeader 还有其他3种样式,可自行选择-->
            <com.scwang.smartrefresh.layout.header.ClassicsHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="20dp"
            android:background="@android:color/white"
            android:text="正在加载"/>
     <!--ClassicsFooter 还有其他2种样式,可自行选择-->
        <com.scwang.smartrefresh.layout.footer.ClassicsFooter
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
            <!--<android.support.v7.widget.RecyclerView-->
                <!--android:id="@+id/recyclerView"-->
                <!--android:layout_width="match_parent"-->
                <!--android:layout_height="match_parent"-->
                <!--android:overScrollMode="never"-->
                <!--android:background="#fff" />-->
        </com.scwang.smartrefresh.layout.SmartRefreshLayout>
    
    

    3、在main中:

    
     RefreshLayout refreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout);
            refreshLayout.setOnRefreshListener(new OnRefreshListener() {
                @Override
                public void onRefresh(RefreshLayout refreshlayout) {
                    refreshlayout.finishRefresh(2000/*,false*/);//传入false表示刷新失败
                    Log.e(TAG, "onRefresh: " );//下拉刷新
                }
            });
            refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
                @Override
                public void onLoadMore(RefreshLayout refreshlayout) {
                    refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
                    Log.e(TAG, "onLoadMore: " );//上拉加载更多
                }
            });
    
    
    

    详细的可参考:https://github.com/scwang90/SmartRefreshLayout

    展开全文
  • GridView,能自定义HeadView和FooterView,不但支持最基本的下拉刷新、上拉加载更多,还在其基础上扩展了是否启用下拉刷新、是否启用上拉加载更多、增加了自动刷新功能,优化了下拉刷新监听器、上拉加载更多监听器,...
  • 博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主威威喵原创,请多支持与指教。 ... 系列文章: ...自定义 View(一)仿 QQ 列表 Item 侧拉删除功能 ...自定义 View(三...
  • 自定义控件

    2017-09-18 20:13:31
    1.自定义view 继承自View完全实现自定义控件是最为自由的一种实现,也是相对来说比较复杂的一种。因为你通常需要正确地测量View的尺寸,并且需要手动绘制各种视觉效果,因此,它的工作量相对来说比较大,...
  • SmartRefreshHorizontal ...简介: 横向刷新、水平刷新、RefreshLayout、OverScroll,Horizontal,基于 SmartRefreshLayout 的水平智能刷新 更多:作者提 Bug官网 标签: 水平刷新-横向刷新- English| 中文...
  • 可以继承RefreshLayout抽象类实现自己的样式 完整的示例项目地址 https://github.com/gengqiquan/RefreshLayout 使用 xml android:id="@+id/refresh" android:layout_width="match_parent" ...
  • 自定义SmartRefreshLayout的Header和Footer

    千次阅读 2019-06-27 17:25:49
    使用SmartRefreshLayout自定义Header并不难,第一个例子重在介绍需要实现的方法,第二个例子为正确的使用示例;Footer类似。 一、经典Header 实现一个最简单的经典Header(没有更新时间) 1.1需求分析 我们的经典...
  • 博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主威威喵原创,请多支持与指教。 ... 系列文章: ...自定义 View(一)仿 QQ 列表 Item 侧拉删除功能 ...自定义 View(三...
  • 安卓常用自定义View

    2017-01-04 14:18:52
    圆形ImageViewimport android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android....
  • Android中自定义刷新头

    2019-06-11 15:47:00
    相信很多小伙伴在开发中都会遇到自定义下拉刷新头和自定义加载尾的需求,下面我分享一下我的自定义刷新头的实现。 这里我用的第三方SmartRefreshLayout,这个实现自定义的时候较为简单,首先导入依赖 //1.1.0 ...
  • 今天上午让实现一下这个效果 ... public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } 具体的状态比较多 可以通过自己需求来使用 None(0,false), ...
  • SwipeRefreshLayout实现各种超炫的下拉刷新酷,适合初学者使用,来源:https://github.com/android-cjj/Android-MaterialRefreshLayout 效果图:

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 818
精华内容 327
关键字:

自定义refreshlayout