分享一个可下拉刷新的ScrollView

原理:就是动态改变ScrollView header的margin实现

主要的代码:

http://blog.csdn.net/swust_chenpeng/article/details/39289721

public class RefreshScrollView extends ScrollView {  

    private final static int SCROLL_DURATION = 400;
    private final static float OFFSET_RADIO = 1.8f;
    private int headerHeight = 0;
    private boolean enableRefresh = true;
    private boolean refreshing = false;
    private int lastY;
    private Scroller scroller = null;
    private OnRefreshScrollViewListener listener = null;
    private LinearLayout scrollContainer = null;
    private ScrollViewHeader headerView = null;  

    public RefreshScrollView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        if (!isInEditMode()) {
            initView(context);
        }
    }  

    public RefreshScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        if (!isInEditMode()) {
            initView(context);
        }
    }  

    public RefreshScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        if (!isInEditMode()) {
            initView(context);
        }
    }  

    /**
     * 初始化view
     */
    private void initView(Context context) {
        scroller = new Scroller(context);
        headerView = new ScrollViewHeader(context);
        LinearLayout.LayoutParams headerViewParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        //scrollview只允许嵌套一个子布局
        scrollContainer = new LinearLayout(context);
        scrollContainer.addView(headerView, headerViewParams);
        scrollContainer.setOrientation(LinearLayout.VERTICAL);
        addView(scrollContainer);
        //提前获取headerView的高度
        headerView.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {  

                    @SuppressWarnings("deprecation")
                    @Override
                    public void onGlobalLayout() {
                        // TODO Auto-generated method stub
                        headerHeight = headerView.getHeight();
                        headerView.updateMargin(-headerHeight);
                        headerView.getViewTreeObserver()
                                .removeGlobalOnLayoutListener(this);
                    }
                });
    }  

    /**
     * 设置内容区域
     *
     * @param context
     * @param resId
     */
    public void setupContainer(Context context, View containerView) {
        scrollContainer.addView(containerView);
    }  

    /**
     * 设置scroll是否可以刷新
     *
     * @param enableRefresh
     */
    public void setEnableRefresh(boolean enableRefresh) {
        this.enableRefresh = enableRefresh;
    }  

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastY = (int) ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            int deltY = (int) (ev.getY() - lastY);
            lastY = (int) ev.getY();
            Logger.d("getScrollY:" + getScrollY());
            if (getScrollY() == 0
                    && (deltY > 0 || headerView.getTopMargin() > -headerHeight)) {
                updateHeader(deltY/OFFSET_RADIO);
                return true;
            }
            break;
        default:
            //这里没有使用action_up的原因是,可能会受到viewpager的影响接收到action_cacel事件
            Logger.d("ev.getAction: " +ev.getAction());
            if (getScrollY() == 0) {
                Logger.d("topMargin():" + headerView.getTopMargin());
                if (headerView.getTopMargin() > 0 && enableRefresh && !refreshing) {
                    refreshing = true;
                    headerView.setState(ScrollViewHeader.STATE_REFRESHING);
                    new Handler().postDelayed(new Runnable() {  

                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            if(listener != null) {
                                listener.onRefresh();
                                refreshing = false;
                                ShowUtils.shortShow("更新成功");
                                resetHeaderView();
                            }
                        }
                    }, 3000);
                }
                Logger.d("resetHeaderView...");
                resetHeaderView();
            }
            break;
        }
        return super.onTouchEvent(ev);
    }  

    /**
     * 更新headerview的高度,同时更改状态
     *
     * @param deltY
     */
    public void updateHeader(float deltY) {
        int currentMargin = (int) (headerView.getTopMargin() + deltY);
        headerView.updateMargin(currentMargin);
        if(enableRefresh && !refreshing) {
            if (currentMargin > 0) {
                headerView.setState(ScrollViewHeader.STATE_READY);
            } else {
                headerView.setState(ScrollViewHeader.STATE_NORMAL);
            }
        }
    }  

    /**
     * 重置headerview的高度
     */
    public void resetHeaderView() {
        int margin = headerView.getTopMargin();
        if(margin == -headerHeight) {
            return ;
        }
        if(margin < 0 && refreshing) {
            //当前已经在刷新,又重新进行拖动,但未拖满,不进行操作
            return ;
        }
        int finalMargin = 0;
        if(margin <= 0 && !refreshing) {
            finalMargin = headerHeight;
        }
        Logger.d("margin: " + margin);
        Logger.d("finalMargin: " + finalMargin);
        //松开刷新,或者下拉刷新,又松手,没有触发刷新
        scroller.startScroll(0, -margin, 0, finalMargin + margin, SCROLL_DURATION);  

        invalidate();
    }  

    /**
     * 开始刷新
     */
    public void startRefresh() {
        refreshing = true;
        headerView.setState(ScrollViewHeader.STATE_REFRESHING);
        if(listener != null) {
            Logger.d("xxx: " + headerHeight);
            scroller.startScroll(0, 0, 0, headerHeight, SCROLL_DURATION);
            invalidate();
            listener.onRefresh();
        }
    }  

    /**
     * 停止刷新
     */
    public void stopRefresh() {
        if(refreshing) {
            refreshing = false;
            resetHeaderView();
        }
    }  

    @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        if(scroller.computeScrollOffset()) {
            Logger.d("getCurrY: " + scroller.getCurrY());
            headerView.updateMargin(-scroller.getCurrY());
            //继续重绘
            postInvalidate();
        }
        super.computeScroll();
    }  

    public void setOnRefreshScrollViewListener(OnRefreshScrollViewListener listener) {
        this.listener = listener;
    }  

    public interface OnRefreshScrollViewListener {
        public void onRefresh();
    }
}  

代码其实还是比较容易,但是但是,自己还是花了很多时间,脑袋瓜不够灵活呀...

下面是ScrollViewHeader的代码:

public class ScrollViewHeader extends RelativeLayout {  

    public final static int STATE_NORMAL = 0;
    public final static int STATE_READY = 1;
    public final static int STATE_REFRESHING = 2;
    private final int ROTATE_ANIM_DURATION = 180;
    private int topMargin = 0;
    private int state = STATE_NORMAL;
    private TextView refreshTv = null;
    private TextView refreshTimeTv = null;
    private ProgressBar refreshProgress = null;
    private ImageView refreshArrow = null;
    private Animation animationUp = null;
    private Animation animationDown = null;  

    public ScrollViewHeader(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        if(!isInEditMode())
            initView(context);
    }  

    public ScrollViewHeader(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        if(!isInEditMode())
            initView(context);
    }  

    public ScrollViewHeader(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        if(!isInEditMode())
            initView(context);
    }  

    /**
     * 初始化相关的view
     */
    public void initView(Context context) {
        animationDown = new RotateAnimation(-180f, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animationDown.setDuration(ROTATE_ANIM_DURATION);
        animationDown.setFillAfter(true);
        animationUp = new RotateAnimation(0, -180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animationUp.setDuration(ROTATE_ANIM_DURATION);
        animationUp.setFillAfter(true);
        setPadding(10, 25, 10, 25);
        View view = LayoutInflater.from(context).inflate(R.layout.scrollview_header, this, true);
        refreshTv = (TextView) view.findViewById(R.id.refresh_text);
        refreshTimeTv = (TextView) view.findViewById(R.id.refresh_time);
        refreshProgress = (ProgressBar) view.findViewById(R.id.refresh_progress);
        refreshArrow = (ImageView) view.findViewById(R.id.refresh_arrow);
    }  

    /**
     * 设置scrollviewHeader的状态
     * @param state
     */
    public void setState(int state) {
        if(this.state == state) {
            return ;
        }
        switch (state) {
        case STATE_NORMAL:
            refreshTv.setText("下拉刷新");
            refreshArrow.setVisibility(View.VISIBLE);
            refreshProgress.setVisibility(View.INVISIBLE);
            if(this.state == STATE_READY) {
                refreshArrow.startAnimation(animationDown);
            } else if(this.state == STATE_REFRESHING) {
                refreshArrow.clearAnimation();
            }
            break;
        case STATE_READY:
            refreshTv.setText("松开刷新");
            refreshArrow.setVisibility(View.VISIBLE);
            refreshProgress.setVisibility(View.INVISIBLE);
            refreshArrow.startAnimation(animationUp);
            break;
        case STATE_REFRESHING:
            refreshTv.setText("正在加载...");
            refreshProgress.setVisibility(View.VISIBLE);
            refreshArrow.clearAnimation();
            refreshArrow.setVisibility(View.INVISIBLE);
            break;
        default:
            break;
        }
        this.state = state;
    }  

    /**
     * 更新header的margin
     * @param margin
     */
    public void updateMargin(int margin) {
        //这里用Linearlayout的原因是Headerview的父控件是scrollcontainer是一个linearlayout
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) this.getLayoutParams();
        params.topMargin = margin;
        topMargin = margin;
        setLayoutParams(params);
    }  

    /**
     * 获取header的margin
     * @return
     */
    public int getTopMargin() {
        return topMargin;
    }
}  

header的布局文件,scrollview_header

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >  

    <LinearLayout
        android:id="@+id/refresh_des"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical" >  

        <TextView
            android:id="@+id/refresh_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新" />  

        <TextView
            android:id="@+id/refresh_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="5分钟前更新" />
    </LinearLayout>  

    <ProgressBar
        android:id="@+id/refresh_progress"
        android:layout_width="30dip"
        android:layout_height="30dip"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dip"
        android:layout_toLeftOf="@id/refresh_des"
        android:visibility="invisible" />  

    <ImageView
        android:id="@+id/refresh_arrow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dip"
        android:layout_toLeftOf="@id/refresh_des"
        android:src="@drawable/arrow" />  

</merge>  

好了,相关的源码就只有3个文件...

时间: 2024-10-06 00:45:04

分享一个可下拉刷新的ScrollView的相关文章

不借助第三方插件利用ScrollView自身delegate实现下拉刷新和上拉加载

下拉刷新功能基本上在所有的app中都会被用到,而且这个功能已经被apple集成进去了,不过必须得是在tableViewController中才有,是一个叫做UIRefreshControl的控件,想看效果可以看手机QQ上面联系人列表下拉后的刷新.这里不多介绍. 本篇blog主要介绍如何在scrollview中实现下拉刷新的效果.因为有些时候我们可能更多地希望直接在scrollview中展现,而不是一定要局限于tableviewcontroller. 当然网上有很多下拉刷新和上拉加载的第三方控件,

scroll-view组件实现下拉刷新, 拉到底加载更多

官方文档已声明,即使在page.json和app.json中开启下拉刷新,scroll-view组件也是不支持的.但我们可以通过曲线救国的方法来实现 实现代码 // wxml <scroll-view style='height: 300px;' scroll-y="{{true}}" scroll-top="{{scrollTop}}" bindscroll="scrollFn" bindscrolltolower="loadM

Android打造通用的下拉刷新组件

还记得上一篇 blog 的内容吗?如果不记得建议先去了解一下,Android 事件处理全面剖析 ,因为下拉刷新需要用到手势的处理,而上一篇文章中,对事件处理做了很详细的说明,了解了事件的处理机制,对理解本篇文章有很大的帮助.好了,这里就当大家都已经对事件处理有了一定的了解,开始我们的下拉刷新征程. 还是老规矩,先上效果图,再根据效果图来分析实现的原理: 一 .分析原理 我们都知道,listView 控件为我们提供了 addHeaderView.和 addFootView 的方法,我们通过此方法可

安卓开发笔记——关于开源组件PullToRefresh实现下拉刷新和上拉加载(一分钟搞定,超级简单)

前言 以前在实现ListView下拉刷新和上拉加载数据的时候都是去继承原生的ListView重写它的一些方法,实现起来非常繁杂,需要我们自己去给ListView定制下拉刷新和上拉加载的布局文件,然后添加ScrollView和OnTouch监听,设置回调接口获取数据,为了更好的交互体验,我们还不能直接利用setVisibility隐藏显示布局等等一大堆的操作,累都累死了.(题外话:关于下拉刷新在新版的android-support-v4.jar里,其实谷歌已经为我们提供了一个控件叫SwipeRef

使用PullToRefresh实现下拉刷新和上拉加载

使用PullToRefresh实现下拉刷新和上拉加载 分类: Android2013-12-20 15:51 78158人阅读 评论(91) 收藏 举报 Android下拉刷新上拉加载PullToRefresh PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的View类型,而且使用起来也十分方便. (下载地址:https://github.com/ch

Android 使用PullToRefresh实现下拉刷新和上拉加载(ExpandableListView)

PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的View类型,而且使用起来也十分方便. (下载地址:https://github.com/chrisbanes/Android-PullToRefresh) 下载完成,将它导入到eclipse中,作为一个library导入到你的工程中就好了. 一.废话少说,下拉刷新Go. 1.在你的布局文件中加上你想用的

Android下拉刷新底部操作栏的隐藏问题

最近自己编写下拉刷新的时候,发现了一个问题,就是有一个需求是这样的:要求页面中是一个Tab切换界面,一个界面有底部操作栏,不可下拉刷新,另一个界面没有底部操作栏,但可以下拉刷新. 按照平常的做法,我在xml文件中使用了RelativeLayout,声明下拉刷新组件的layout_above为底部操作栏,然后在测试的时候发现一个奇怪的现象:如果一开始设置底部操作栏可见,在另一个运行下拉刷新的界面在下拉的时候就会出现和底部操作栏同样位置,同样大小,但颜色采用系统默认的布局,如果设置为不可见,则不会出

Android自定义控件--下拉刷新的实现

我们在使用ListView的时候,很多情况下需要用到下拉刷新的功能.为了了解下拉刷新的底层实现原理,我采用自定义ListView控件的方式来实现效果. 实现的基本原理是:自定义ListView,给ListView加载头布局,然后动态的控制头布局的现实与隐藏.ListView初始化的时候,头布局是隐藏的,当手指往下拉的时候,根据手指移动的距离与头布局的高度的关系来控制头布局的显示.具体的控制思路详见后边的代码,代码中的注释很详细.先来看一下效果图:                     头布局的

Maxwin-z/XListView-Android(下拉刷新上拉加载)源码解析(一)

本次解析的内容,是github上一个用于下拉刷新上拉加载的控件xlistview,这个功能相信大家在开发的过程中会经常用到. 控件的源码地址是https://github.com/Maxwin-z/XListView-Android 在这个控件之前,我看过一些相同功能的控件,挑选后觉得XListView功能比较完善,而且易于理解.在android-open-project里面,有提到一个DropDownListView,个人使用过以后,觉得功能是具备了,但是操作体验不好,原因就是没有使用到Scr