`
xwangly
  • 浏览: 130704 次
  • 性别: Icon_minigender_1
  • 来自: 鄂州
社区版块
存档分类
最新评论

方便实用的下拉刷新控件,支持ScrollView、AbsListView

阅读更多

最近要做一个下拉刷新的功能,网上找了很多例子,也看了一些开源的下拉刷新项目,但是小例子比较简单,效果和稳定性都差强人意,而开源的项目又太庞大,看起来耗时费劲,所以只好综合一下各处的代码掌握其原理,自己实现一套下拉刷新功能。

 

该控件特点:

1.子控件必须是一个ScrollView或ListView;

2.支持自定义下拉布局;

3.自定义下拉布局可以不用处理下拉的各种状态(只需要实现几个接口即可),也可以自己处理各种下拉的状态。

 

先来看看效果图:



 



 



 

上代码:

首先看如何使用:

1.使用的布局:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.pulldown.PullDownScrollView
        android:id="@+id/refresh_root"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:background="#161616"
        android:orientation="vertical" >

        <ScrollView
            android:id="@+id/scrollview"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:scrollbars="none" >

            <LinearLayout
                android:id="@+id/mainView"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="#1f1f1f"
                android:orientation="vertical" >

                <!-- 自已的布局 -->

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dip"
                    android:gravity="center"
                    android:text="@string/hello_world"
                    android:textColor="@android:color/white"
                    android:textSize="18sp" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dip"
                    android:gravity="center"
                    android:text="@string/hello_world"
                    android:textColor="@android:color/white"
                    android:textSize="18sp" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dip"
                    android:gravity="center"
                    android:text="@string/hello_world"
                    android:textColor="@android:color/white"
                    android:textSize="18sp" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dip"
                    android:gravity="center"
                    android:text="@string/hello_world"
                    android:textColor="@android:color/white"
                    android:textSize="18sp" />
            </LinearLayout>
        </ScrollView>
    </com.example.pulldown.PullDownScrollView>

</LinearLayout>

 2.UI使用:

   首先,Activity实现接口:

implements RefreshListener

 部分代码如下:

  package com.example.pulldown;

import com.example.pulldown.PullDownScrollView.RefreshListener;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity implements RefreshListener{
    private PullDownScrollView mPullDownScrollView; 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mPullDownScrollView = (PullDownScrollView) findViewById(R.id.refresh_root);  
        mPullDownScrollView.setRefreshListener(this);  
        mPullDownScrollView.setPullDownElastic(new PullDownElasticImp(this));  
    }
    @Override  
    public void onRefresh(PullDownScrollView view) {  
        new Handler().postDelayed(new Runnable() {  
              
            @Override  
            public void run() {  
                // TODO Auto-generated method stub  
                mPullDownScrollView.finishRefresh("上次刷新时间:12:23");  
            }  
        }, 2000);  
    }  
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}
 

3.再来看看控件代码:

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.ScrollView;


/**
 * @author xwangly@163.com
 * @date 2013-7-9
 * 
 */
public class PullDownScrollView extends LinearLayout {

    private static final String TAG = "PullDownScrollView";

    private int refreshTargetTop = -60;
    private int headContentHeight;

    private RefreshListener refreshListener;

    private RotateAnimation animation;
    private RotateAnimation reverseAnimation;
    
    private final static int RATIO = 2;
    private int preY = 0;
    private boolean isElastic = false;
    private int startY;
    private int state;
    
    private String note_release_to_refresh = "松开更新";
    private String note_pull_to_refresh = "下拉刷新";
    private String note_refreshing = "正在更新...";
    
    private IPullDownElastic mElastic;
    

    public PullDownScrollView(Context context) {
        super(context);
        init();

    }

    public PullDownScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        animation = new RotateAnimation(0, -180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        animation.setInterpolator(new LinearInterpolator());
        animation.setDuration(250);
        animation.setFillAfter(true);

        reverseAnimation = new RotateAnimation(-180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        reverseAnimation.setInterpolator(new LinearInterpolator());
        reverseAnimation.setDuration(200);
        reverseAnimation.setFillAfter(true);
    }
    /**
     * 刷新监听
     * @param listener
     */
    public void setRefreshListener(RefreshListener listener) {
        this.refreshListener = listener;
    }
    /**
     * 下拉布局
     * @param elastic
     */
    public void setPullDownElastic(IPullDownElastic elastic) {
        mElastic = elastic;
        
        headContentHeight = mElastic.getElasticHeight();
        refreshTargetTop = - headContentHeight;
        LayoutParams lp = new LinearLayout.LayoutParams(
                LayoutParams.FILL_PARENT, headContentHeight);
        lp.topMargin = refreshTargetTop;
        addView(mElastic.getElasticLayout(), 0, lp);
    }
    
    /**
     * 设置更新提示语
     * @param pullToRefresh 下拉刷新提示语
     * @param releaseToRefresh 松开刷新提示语
     * @param refreshing 正在刷新提示语
     */
    public void setRefreshTips(String pullToRefresh, String releaseToRefresh, String refreshing) {
        note_pull_to_refresh = pullToRefresh;
        note_release_to_refresh = releaseToRefresh;
        note_refreshing = refreshing;
    }
    /*
     * 该方法一般和ontouchEvent 一起用 (non-Javadoc)
     * 
     * @see
     * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Logger.d(TAG, "onInterceptTouchEvent");
        printMotionEvent(ev);
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            preY = (int) ev.getY();
        }
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {

            Logger.d(TAG, "isElastic:" + isElastic + " canScroll:"+ canScroll() + " ev.getY() - preY:"+(ev.getY() - preY));
            if (!isElastic && canScroll()
                    && (int) ev.getY() - preY >= headContentHeight / (3*RATIO)
                    && refreshListener != null && mElastic != null) {

                isElastic = true;
                startY = (int) ev.getY();
                Logger.i(TAG, "在move时候记录下位置startY:" + startY);
                return true;
            }

        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Logger.d(TAG, "onTouchEvent");
        printMotionEvent(event);
        handleHeadElastic(event);
        return super.onTouchEvent(event);
    }

    private void handleHeadElastic(MotionEvent event) {
        if (refreshListener != null && mElastic != null) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Logger.i(TAG, "down");
                break;
            case MotionEvent.ACTION_UP:

                Logger.i(TAG, "up");
                if (state != IPullDownElastic.REFRESHING && isElastic) {
                    
                    if (state == IPullDownElastic.DONE) {
                        // 什么都不做
                        setMargin(refreshTargetTop);
                    }
                    if (state == IPullDownElastic.PULL_To_REFRESH) {
                        state = IPullDownElastic.DONE;
                        setMargin(refreshTargetTop);
                        changeHeaderViewByState(state, false);
                        Logger.i(TAG, "由下拉刷新状态,到done状态");
                    }
                    if (state == IPullDownElastic.RELEASE_To_REFRESH) {
                        state = IPullDownElastic.REFRESHING;
                        setMargin(0);
                        changeHeaderViewByState(state, false);
                        onRefresh();
                        Logger.i(TAG, "由松开刷新状态,到done状态");
                    }

                }
                isElastic = false;
                break;
            case MotionEvent.ACTION_MOVE:
                Logger.i(TAG, "move");
                int tempY = (int) event.getY();
                
                if (state != IPullDownElastic.REFRESHING && isElastic) {
                    // 可以松手去刷新了
                    if (state == IPullDownElastic.RELEASE_To_REFRESH) {
                        if (((tempY - startY) / RATIO < headContentHeight)
                                && (tempY - startY) > 0) {
                            state = IPullDownElastic.PULL_To_REFRESH;
                            changeHeaderViewByState(state, true);
                            Logger.i(TAG, "由松开刷新状态转变到下拉刷新状态");
                        } else if (tempY - startY <= 0) {
                            state = IPullDownElastic.DONE;
                            changeHeaderViewByState(state, false);
                            Logger.i(TAG, "由松开刷新状态转变到done状态");
                        }
                    }
                    if (state == IPullDownElastic.DONE) {
                        if (tempY - startY > 0) {
                            state = IPullDownElastic.PULL_To_REFRESH;
                            changeHeaderViewByState(state, false);
                        }
                    }
                    if (state == IPullDownElastic.PULL_To_REFRESH) {
                        // 下拉到可以进入RELEASE_TO_REFRESH的状态
                        if ((tempY - startY) / RATIO >= headContentHeight) {
                            state = IPullDownElastic.RELEASE_To_REFRESH;
                            changeHeaderViewByState(state, false);
                            Logger.i(TAG, "由done或者下拉刷新状态转变到松开刷新");
                        } else if (tempY - startY <= 0) {
                            state = IPullDownElastic.DONE;
                            changeHeaderViewByState(state, false);
                            Logger.i(TAG, "由DOne或者下拉刷新状态转变到done状态");
                        }
                    }
                    if (tempY - startY > 0) {
                        setMargin((tempY - startY)/2 + refreshTargetTop);
                    }
                }
                break;
            }
        }
    }
    
    /**
     * 
     */
    private void setMargin(int top) {
        LinearLayout.LayoutParams lp = (LayoutParams) mElastic.getElasticLayout()
                .getLayoutParams();
        lp.topMargin = top;
        // 修改后刷新
        mElastic.getElasticLayout().setLayoutParams(lp);
        mElastic.getElasticLayout().invalidate();
    }

    private void changeHeaderViewByState(int state, boolean isBack) {

        mElastic.changeElasticState(state, isBack);
        switch (state) {
        case IPullDownElastic.RELEASE_To_REFRESH:
            mElastic.showArrow(View.VISIBLE);
            mElastic.showProgressBar(View.GONE);
            mElastic.showLastUpdate(View.VISIBLE);
            mElastic.setTips(note_release_to_refresh);

            mElastic.clearAnimation();
            mElastic.startAnimation(animation);
            Logger.i(TAG, "当前状态,松开刷新");
            break;
        case IPullDownElastic.PULL_To_REFRESH:
            mElastic.showArrow(View.VISIBLE);
            mElastic.showProgressBar(View.GONE);
            mElastic.showLastUpdate(View.VISIBLE);
            mElastic.setTips(note_pull_to_refresh);

            mElastic.clearAnimation();

            // 是由RELEASE_To_REFRESH状态转变来的
            if (isBack) {
                mElastic.startAnimation(reverseAnimation);
            }
            Logger.i(TAG, "当前状态,下拉刷新");
            break;
        case IPullDownElastic.REFRESHING:
            mElastic.showArrow(View.GONE);
            mElastic.showProgressBar(View.VISIBLE);
            mElastic.showLastUpdate(View.GONE);
            mElastic.setTips(note_refreshing);

            mElastic.clearAnimation();
            Logger.i(TAG, "当前状态,正在刷新...");
            break;
        case IPullDownElastic.DONE:
            mElastic.showProgressBar(View.GONE);
            mElastic.clearAnimation();
//            arrowImageView.setImageResource(R.drawable.goicon);
            // tipsTextview.setText("下拉刷新");
            // lastUpdatedTextView.setVisibility(View.VISIBLE);
            Logger.i(TAG, "当前状态,done");
            break;
        }
    }

    private void onRefresh() {
        // downTextView.setVisibility(View.GONE);
//        scroller.startScroll(0, i, 0, 0 - i);
//        invalidate();
        if (refreshListener != null) {
            refreshListener.onRefresh(this);
        }
    }

    /**
     * 
     */
    @Override
    public void computeScroll() {
//        if (scroller.computeScrollOffset()) {
//            int i = this.scroller.getCurrY();
//            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.refreshView
//                    .getLayoutParams();
//            int k = Math.max(i, refreshTargetTop);
//            lp.topMargin = k;
//            this.refreshView.setLayoutParams(lp);
//            this.refreshView.invalidate();
//            invalidate();
//        }
    }

    /**
     * 结束刷新事件,UI刷新完成后必须回调此方法
     * @param text 一般传入:“上次更新时间:12:23”
     */
    public void finishRefresh(String text) {
        if (mElastic == null) {
            Logger.d(TAG, "finishRefresh mElastic:" + mElastic);
            return;
        }
        state = IPullDownElastic.DONE;
        mElastic.setLastUpdateText(text);
        changeHeaderViewByState(state,false);
        Logger.i(TAG, "执行了=====finishRefresh");

        mElastic.showArrow(View.VISIBLE);
        mElastic.showLastUpdate(View.VISIBLE);
        setMargin(refreshTargetTop);
//        scroller.startScroll(0, i, 0, refreshTargetTop);
//        invalidate();
    }

    private boolean canScroll() {
        View childView;
        if (getChildCount() > 1) {
            childView = this.getChildAt(1);
            if (childView instanceof AbsListView) {
                int top = ((AbsListView) childView).getChildAt(0).getTop();
                int pad = ((AbsListView) childView).getListPaddingTop();
                if ((Math.abs(top - pad)) < 3
                        && ((AbsListView) childView).getFirstVisiblePosition() == 0) {
                    return true;
                } else {
                    return false;
                }
            } else if (childView instanceof ScrollView) {
                if (((ScrollView) childView).getScrollY() == 0) {
                    return true;
                } else {
                    return false;
                }
            }

        }
        return canScroll(this);
    }
    
    /**
     * 子类重写此方法可以兼容其它的子控件,目前只兼容AbsListView和ScrollView
     * @param view
     * @return
     */
    public boolean canScroll(PullDownScrollView view) {
        return false;
    }

    private void printMotionEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Logger.d(TAG, "down");
            break;
        case MotionEvent.ACTION_MOVE:
            Logger.d(TAG, "move");
            break;
        case MotionEvent.ACTION_UP:
            Logger.d(TAG, "up");
        default:
            break;
        }
    }
    /**
     * 刷新监听接口
     */
    public interface RefreshListener {
        public void onRefresh(PullDownScrollView view);
    }

}

 

4.接口:

import android.view.View;
import android.view.animation.Animation;

/**
 * @author xwangly@163.com
 * @date 2013-7-10
 * 下拉控件接口
 */
public interface IPullDownElastic {
    public final static int RELEASE_To_REFRESH = 0;
    public final static int PULL_To_REFRESH = 1;
    public final static int REFRESHING = 2;
    public final static int DONE = 3;

    public View getElasticLayout();

    public int getElasticHeight();

    public void showArrow(int visibility);

    public void startAnimation(Animation animation);

    public void clearAnimation();

    public void showProgressBar(int visibility);

    public void setTips(String tips);

    public void showLastUpdate(int visibility);

    public void setLastUpdateText(String text);
    
    /**
     * 可以不用实现此方法,PullDownScrollView会处理ElasticLayout布局中的状态 
     * 如果需要特殊处理,可以实现此方法进行处理
     * 
     * @param state  @see RELEASE_To_REFRESH
     * @param isBack 是否是松开回退
     */
    public void changeElasticState(int state, boolean isBack);

}

 5.默认实现:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;


/**
 * @author xwangly@163.com
 * @date   2013-7-10
 * 默认下拉控件布局实现
 */
public class PullDownElasticImp implements IPullDownElastic {
    private View refreshView;
    private ImageView arrowImageView;
    private int headContentHeight;
    private ProgressBar progressBar;
    private TextView tipsTextview;
    private TextView lastUpdatedTextView;
    
    private Context mContext;
    public PullDownElasticImp(Context context) {
        mContext = context;
        init();
    }
    

    private void init() {
        // 刷新视图顶端的的view
        refreshView = LayoutInflater.from(mContext).inflate(
                R.layout.refresh_top_item, null);

        // 指示器view
        arrowImageView = (ImageView) refreshView
                .findViewById(R.id.head_arrowImageView);
        // 刷新bar
        progressBar = (ProgressBar) refreshView
                .findViewById(R.id.head_progressBar);
        // 下拉显示text
        tipsTextview = (TextView) refreshView.findViewById(R.id.refresh_hint);
        // 下来显示时间
        lastUpdatedTextView = (TextView) refreshView
                .findViewById(R.id.refresh_time);

        headContentHeight = Utils.dip2px(mContext, 50);
    }

    /**
     * @return
     * 
     */
    @Override
    public View getElasticLayout() {
        return refreshView;
    }

    /**
     * @return
     * 
     */
    @Override
    public int getElasticHeight() {
        return headContentHeight;
    }

    /**
     * @param show
     * 
     */
    @Override
    public void showArrow(int visibility) {
        arrowImageView.setVisibility(visibility);
    }

    /**
     * @param animation
     * 
     */
    @Override
    public void startAnimation(Animation animation) {
        arrowImageView.startAnimation(animation);
    }

    /**
     * 
     * 
     */
    @Override
    public void clearAnimation() {
        arrowImageView.clearAnimation();
    }

    /**
     * @param show
     * 
     */
    @Override
    public void showProgressBar(int visibility) {
        progressBar.setVisibility(visibility);
    }

    /**
     * @param tips
     * 
     */
    @Override
    public void setTips(String tips) {
        tipsTextview.setText(tips);
    }

    /**
     * @param show
     * 
     */
    @Override
    public void showLastUpdate(int visibility) {
        lastUpdatedTextView.setVisibility(visibility);
    }

    /**
     * @param text
     * 
     */
    public void setLastUpdateText(String text) {
        lastUpdatedTextView.setText(text);
    }


    /**
     * @param state
     * @param isBack
     * 
     */
    @Override
    public void changeElasticState(int state, boolean isBack) {
        // TODO Auto-generated method stub
        
    }

}

 

6.默认实现的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="-50.0dip"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0.0dip"
        android:layout_weight="1.0"
        android:gravity="center"
        android:orientation="horizontal" >

        <!-- 箭头图像、进度条 -->

        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dip" >

            <!-- 箭头 -->

            <ImageView
                android:id="@+id/head_arrowImageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@drawable/goicon" />

            <!-- 进度条 -->

            <ProgressBar
                android:id="@+id/head_progressBar"
                style="@android:style/Widget.ProgressBar.Small.Inverse"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone" />
        </FrameLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:gravity="center"
            android:orientation="vertical" >

            <!-- 提示 -->

            <TextView
                android:id="@+id/refresh_hint"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉刷新"
                android:textColor="#f2f2f2"
                android:textSize="16sp" />

            <!-- 最近更新 -->

            <TextView
                android:id="@+id/refresh_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="上次更新"
                android:textColor="#b89766"
                android:textSize="10sp" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

 6.图片资源:

@drawable/goicon



 

 

完结

 

 

 修改:从原工程中独立出来,并附上简单的工程,见附件。

  • 大小: 406 Bytes
  • 大小: 24.4 KB
  • 大小: 25.2 KB
  • 大小: 23.1 KB
2
2
分享到:
评论
5 楼 xingfuyizhan 2016-05-19  
写的很好,谢谢分享
4 楼 xingfuyizhan 2016-05-19  
在Fragment中使用布局怎么显示不出来
3 楼 xhAndroid 2013-12-09  
加入我工程的时候有个诡异的bug,下拉刷新布局就会覆盖主View,
我的布局里面就是前面加了个title,把title的初始化和设置监听注释掉就不会有这个BUG了,太诡异了
2 楼 xhAndroid 2013-10-30  
1 楼 freezingsky 2013-07-10  
写得很好。继续努力!

相关推荐

Global site tag (gtag.js) - Google Analytics