安卓弹性刷新通用版—支持任何view上下刷新均有弹性效果

  做安卓开发的朋友们,不知道你们会不会经常用遇到这样的情景,某个做所畏的产品设计的sb,拿着iphone来看给你看,说看苹果的这个效果多好,看那个效果多好。苹果也比安卓清晰多了,你能不能也把咱们的安卓应用也做成这种效果的。那样的用户体验更好,更cool一些。不知你们有没有遇到过这样半吊子的sb设计,反正我是遇到了。所以本文由此而生。

  进入正题:首先你要实现弹性效果的view要能确定什么时候应该出现下拉的效果,什么时候出现下推的效果。在代码里的体现就是你要实现IScrollOverable接口。本文中的例子就拿GridView来做个例子。

  无图无真相:

  

  实现了IScrollOverable接口的GridView:

  

 1 public class BshSOGridView extends GridView implements
 2         IScrollOverable
 3 {
 4     public BshSOGridView(Context context, AttributeSet attrs,
 5             int defStyle)
 6     {
 7         super( context, attrs, defStyle );
 8     }
 9 
10     public BshSOGridView(Context context, AttributeSet attrs)
11     {
12         super( context, attrs );
13     }
14 
15     public BshSOGridView(Context context)
16     {
17         super( context );
18     }
19 
20     @Override
21     public boolean isScrollOnTop()
22     {
23         return 0 == getFirstVisiblePosition() ? true : false;
24     }
25 
26     @Override
27     public boolean isScrollOnBtm()
28     {
29         return (getCount() - 1) == getLastVisiblePosition() ? true : false;
30     }
31 }

  

  处理弹性效果的view.实际上是把想要有弹性效果的view加到这个view里来。

/**
 * 本类旨在实现通用的弹性刷新效果。只要实现了IScrollOverable的view都可以据有弹性效果。上弹下弹均可以哦!
 * 
 * @author http://www.cnblogs.com/bausch/
 * 
 */
public class BshElasticView extends LinearLayout
{
    final int RELEASE_H = 0;
    final int PULL = 1;
    final int REFRESHING_H = 2;
    final int PUSH = 3;
    final int RELEASE_F = 4;
    final int REFRESHING_F = 5;
    final int NORMAL = 6;
    int factor = 3;
    float maxElastic = 0.2f;
    int critical = 2;
    int maxElasticH, maxElasticF;
    boolean isTopRecored;
    boolean isBtmRecored = false;
    int startY;
    IRefresh irefresh;
    RotateAnimation animation;
    RotateAnimation reverseAnimation;
    int state = NORMAL;
    boolean isBack;

    View rv;
    RelativeLayout headerView, footerView, continer, main_continer;
    ProgressBar hPro, fPro;
    ImageView hArow, fArow;
    TextView tvTipH, tvTipF, tvLstH, tvLstF;
    Context ctx;
    private IScrollOverable overable;
    public int continerH, continerW, headerH, headerW, footerH, footerW, rvH,
            rvW;
    Handler handler = new Handler()
    {

        @Override
        public void handleMessage(Message msg)
        {
            switch ( msg.what )
            {
            case 0:
                Log.d( "bsh", "刷新完成,到normal状态" );
                state = NORMAL;
                changeFooterViewByState();
                break;
            }
        }

    };

    public BshElasticView(Context context, AttributeSet attrs, int defStyle)
    {
        super( context, attrs );
        init( context );
    }

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

    public BshElasticView(Context context)
    {
        super( context );
        init( context );
    }

    public void setScrollOverable(IScrollOverable o)
    {
        overable = o;
        addView( ( View ) o );
    }

    public void init(Context ctx)
    {
        this.ctx = ctx;
        rv = View.inflate( ctx, R.layout.elastic_view, null );
        main_continer = ( RelativeLayout ) rv.findViewById( R.id.main_continer );

        headerView = ( RelativeLayout ) rv.findViewById( R.id.header );
        hPro = ( ProgressBar ) rv.findViewById( R.id.pro_h );
        hArow = ( ImageView ) rv.findViewById( R.id.iv_harow );
        tvTipH = ( TextView ) rv.findViewById( R.id.tv_htip );
        tvLstH = ( TextView ) rv.findViewById( R.id.tv_lst );
        measureViewHeight( headerView );
        headerH = headerView.getMeasuredHeight();
        headerW = headerView.getMeasuredWidth();
        footerView = ( RelativeLayout ) rv.findViewById( R.id.footer );
        fPro = ( ProgressBar ) rv.findViewById( R.id.pro_f );
        fArow = ( ImageView ) rv.findViewById( R.id.iv_farow );
        tvTipF = ( TextView ) rv.findViewById( R.id.tv_ftip );
        tvLstF = ( TextView ) rv.findViewById( R.id.tv_lstf );
        footerH = headerH;
        footerW = headerW;

        continer = ( RelativeLayout ) rv.findViewById( R.id.continer );
        addView( rv, new ViewGroup.LayoutParams( LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT ) );
        main_continer.setPadding( 0, -1 * headerH, 0, 0 );
        rv.invalidate();
        rv.requestLayout();

        animation = new RotateAnimation( 0, -180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f );
        animation.setDuration( 500 );
        animation.setFillAfter( true );

        reverseAnimation = new RotateAnimation( -180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f );
        reverseAnimation.setDuration( 500 );
        reverseAnimation.setFillAfter( true );

        state = NORMAL;
    }

    private void measureViewHeight(View child)
    {
        ViewGroup.LayoutParams p = child.getLayoutParams();

        if ( null == p )
        {
            p = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.FILL_PARENT );
        }

        int childHeightSpec = ViewGroup
                .getChildMeasureSpec( 0, 0 + 0, p.height );
        int lpWidth = p.width;
        int childWidthSpec;
        if ( lpWidth > 0 )
        {
            childWidthSpec = MeasureSpec.makeMeasureSpec( lpWidth,
                    MeasureSpec.EXACTLY );
        }
        else
        {
            childWidthSpec = MeasureSpec.makeMeasureSpec( 0,
                    MeasureSpec.UNSPECIFIED );
        }
        child.measure( childWidthSpec, childHeightSpec );
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout( changed, l, t, r, b );
        rvH = b;
        rvW = r;
        footerH = headerH;
        footerW = headerW;
        continerH = rvH;
        continerW = rvW;
        maxElasticH = ( int ) (headerH + continerH * maxElastic);
        maxElasticF = 0 - maxElasticH;
        RelativeLayout.LayoutParams rl = new RelativeLayout.LayoutParams( rvW,
                rvH );
        rl.addRule( RelativeLayout.BELOW, R.id.header );
        continer.setLayoutParams( rl );
        continer.requestLayout();
    }

    public interface IScrollOverable
    {
        public boolean isScrollOnTop();

        public boolean isScrollOnBtm();
    }

    public void addView(View v)
    {
        continer.addView( v, new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.FILL_PARENT,
                RelativeLayout.LayoutParams.FILL_PARENT ) );
    }

    public void changeHeaderViewByState()
    {
        switch ( state )
        {
        case RELEASE_H:
            hArow.setVisibility( View.VISIBLE );
            hPro.setVisibility( View.GONE );
            tvTipH.setVisibility( View.VISIBLE );
            tvLstH.setVisibility( View.VISIBLE );
            hArow.clearAnimation();
            hArow.startAnimation( animation );
            tvTipH.setText( "松开刷新" );
            Log.d( "bsh", "当前状态,松开刷新" );
            break;
        case PULL:
            hPro.setVisibility( View.GONE );
            tvTipH.setVisibility( View.VISIBLE );
            tvLstH.setVisibility( View.VISIBLE );
            hArow.clearAnimation();
            hArow.setVisibility( View.VISIBLE );
            if ( isBack )
            {
                isBack = false;
                hArow.clearAnimation();
                hArow.startAnimation( reverseAnimation );
            }
            tvTipH.setText( "下拉刷新" );
            Log.d( "bsh", "当前状态,上推刷新" );
            break;
        case REFRESHING_H:
            hPro.setVisibility( View.VISIBLE );
            hArow.clearAnimation();
            hArow.setVisibility( View.GONE );
            tvTipH.setText( "正在刷新..." );
            tvLstH.setVisibility( View.INVISIBLE );
            tvLstH.setText( "上次更新:"
                    + Calendar.getInstance().getTime().toLocaleString() );
            Log.d( "bsh", "当前状态,正在刷新..." );
            break;
        case NORMAL:
            setRvPadding( -1 * headerH );
            hPro.setVisibility( View.GONE );
            hArow.clearAnimation();
            hArow.setImageResource( R.drawable.arrow_down );
            tvTipH.setText( "下拉刷新" );
            tvLstH.setVisibility( View.VISIBLE );
            Log.d( "bsh", "当前状态,normalf" );
            break;
        }
    }

    public void changeFooterViewByState()
    {
        switch ( state )
        {
        case RELEASE_F:
            fArow.setVisibility( View.VISIBLE );
            fPro.setVisibility( View.GONE );
            tvTipF.setVisibility( View.VISIBLE );
            tvLstF.setVisibility( View.VISIBLE );
            fArow.clearAnimation();
            fArow.startAnimation( animation );
            tvTipF.setText( "松开刷新" );
            Log.d( "bsh", "当前状态,松开刷新" );
            break;
        case PUSH:
            fPro.setVisibility( View.GONE );
            tvTipF.setVisibility( View.VISIBLE );
            tvLstF.setVisibility( View.VISIBLE );
            fArow.clearAnimation();
            fArow.setVisibility( View.VISIBLE );
            if ( isBack )
            {
                isBack = false;
                fArow.clearAnimation();
                fArow.startAnimation( reverseAnimation );
            }
            tvTipF.setText( "上推刷新" );
            Log.d( "bsh", "当前状态,上推刷新" );
            break;
        case REFRESHING_F:
            fPro.setVisibility( View.VISIBLE );
            fArow.clearAnimation();
            fArow.setVisibility( View.GONE );
            tvTipF.setText( "正在刷新..." );
            tvLstF.setVisibility( View.INVISIBLE );
            tvLstF.setText( "上次更新:"
                    + Calendar.getInstance().getTime().toLocaleString() );
            Log.d( "bsh", "当前状态,正在刷新..." );
            break;
        case NORMAL:
            setRvPadding( -1 * headerH );
            fPro.setVisibility( View.GONE );
            fArow.clearAnimation();
            fArow.setImageResource( R.drawable.arrow_up );
            tvTipF.setText( "上推刷新" );
            tvLstF.setVisibility( View.VISIBLE );
            Log.d( "bsh", "当前状态,normalf" );
            break;
        }
    }

    public void setRvPadding(int padding)
    {
        Log.d( "bsh", "padding:" + padding );
        if ( padding < maxElasticF )
        {
            return;
        }
        else if ( padding > maxElasticH )
        {
            return;
        }

        main_continer.setPadding( 0, padding, 0, 0 );
        rv.requestLayout();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event)
    {
        switch ( event.getAction() )
        {
        case MotionEvent.ACTION_DOWN:
            startRecord( event );
            break;
        case MotionEvent.ACTION_UP:
            handleUpF();
            handleUpH();
            break;
        case MotionEvent.ACTION_MOVE:
            handleMove( event );
            break;
        }
        if ( state == NORMAL || state == REFRESHING_H || state == REFRESHING_F )
        {
            return super.dispatchTouchEvent( event );
        }
        else
            return true;
    }

    public void handleMove(MotionEvent event)
    {
        int tempY = ( int ) event.getY();
        tempY = tempY < 0 ? 0 : tempY;
        Log.d( "bsh", "get temp:" + tempY );
        startRecord( event );
        if ( state != REFRESHING_F && isBtmRecored )
        {
            handleMoveF( tempY );
        }
        else if ( state != REFRESHING_H && isTopRecored )
        {
            handleMoveH( tempY );
        }
    }

    public void handleMoveH(int tempY)
    {
        Log.d( "bsh", "release to refresh h" );
        if ( state == RELEASE_H )
        {
            Log.d( "bsh", "release to refresh h" );
            if ( ((tempY - startY) / factor < headerH * critical)
                    && (tempY - startY) > 0 )
            {
                state = PULL;
                changeHeaderViewByState();
                Log.d( "bsh", "由松开刷新状态转变到下拉刷新状态" );
            }
            else if ( startY - tempY > 0 )
            {
                state = NORMAL;
                changeHeaderViewByState();
                Log.d( "bsh", "由松开刷新状态转变到normal状态" );
            }
        }
        if ( state == PULL )
        {
            Log.d( "bsh", "push to refresh" );
            if ( (tempY - startY) / factor >= headerH * critical )
            {
                state = RELEASE_H;
                isBack = true;
                changeHeaderViewByState();
                Log.d( "bsh", "由normal或者下拉刷新状态转变到松开刷新" );
            }
            else if ( tempY - startY <= 0 )
            {
                state = NORMAL;
                changeHeaderViewByState();
                Log.d( "bsh", "由normal或者下拉刷新状态转变到normal状态" );
            }
        }

        if ( state == NORMAL )
        {
            if ( tempY - startY > 0 )
            {
                Log.d( "bsh", "normalf to push to refersh" );
                state = PULL;
                changeHeaderViewByState();
            }
        }

        // 这里就是处理弹性效果的地方
        if ( state == PULL || state == RELEASE_H )
        {
            setRvPadding( (tempY - startY) / factor - headerH );
        }
    }

    public void handleMoveF(int tempY)
    {
        if ( state == RELEASE_F )
        {
            Log.d( "bsh", "release to refresh f" );
            if ( ((tempY - startY) / factor > 0 - headerH * critical)
                    && (tempY - startY) < 0 )
            {
                state = PUSH;
                changeFooterViewByState();
                Log.d( "bsh", "由松开刷新状态转变到上推刷新状态" );
            }
            else if ( tempY - startY >= 0 )
            {
                state = NORMAL;
                changeFooterViewByState();
                Log.d( "bsh", "由松开刷新状态转变到normal状态" );
            }
        }
        if ( state == PUSH )
        {
            Log.d( "bsh", "push to refresh" );
            if ( (tempY - startY) / factor <= (0 - headerH * critical) )
            {
                state = RELEASE_F;
                isBack = true;
                changeFooterViewByState();
                Log.d( "bsh", "由normal或者上推刷新状态转变到松开刷新" );
            }
            else if ( tempY - startY >= 0 )
            {
                state = NORMAL;
                changeFooterViewByState();
                Log.d( "bsh", "由normal或者上推刷新状态转变到normal状态" );
            }
        }

        if ( state == NORMAL )
        {
            if ( tempY - startY < 0 )
            {
                Log.d( "bsh", "normalf to push to refersh" );
                state = PUSH;
                changeFooterViewByState();
            }
        }

        // 这里就是处理弹性效果的地方。
        if ( state == PUSH || state == RELEASE_F )
        {
            setRvPadding( (tempY - startY) / factor - headerH * 2 );
        }
    }

    public void startRecord(MotionEvent event)
    {
        if ( overable.isScrollOnTop() && !isTopRecored )
        {
            isTopRecored = true;
            startY = ( int ) event.getY();
        }
        if ( overable.isScrollOnBtm() && !isBtmRecored )
        {
            isBtmRecored = true;
            startY = ( int ) event.getY();
        }
    }

    public void handleUpF()
    {
        if ( state != REFRESHING_F )
        {
            if ( state == PUSH )
            {
                state = NORMAL;
                changeFooterViewByState();
                Log.d( "bsh", "由上推刷新状态,到normal状态" );
            }

            if ( state == RELEASE_F )
            {
                state = REFRESHING_F;
                changeFooterViewByState();
                setRvPadding( -1 * headerH - 1 );
                irefresh.refreshBtm();
            }
        }
        isBtmRecored = false;
        isBack = false;
    }

    public void handleUpH()
    {
        if ( state != REFRESHING_H )
        {
            if ( state == PULL )
            {
                state = NORMAL;
                changeHeaderViewByState();
                Log.d( "bsh", "由下拉状态,到normal状态" );
            }

            if ( state == RELEASE_H )
            {
                state = REFRESHING_H;
                changeHeaderViewByState();
                setRvPadding( 0 );
                irefresh.refreshTop();
            }
        }
        isTopRecored = false;
        isBack = false;
    }

    public void onRefreshComplete()
    {
        handler.sendEmptyMessage( 0 );
    }

    public interface IRefresh
    {
        public boolean refreshTop();

        public boolean refreshBtm();
    }

    /***
     * 设置拉动效果幅度建议值(1-5)
     * 
     * @param factor
     */
    public void setFactor(int factor)
    {
        this.factor = factor;
    }

    /***
     * 设置最大拉动的位置,请在(0.0f-1.0f)之间取值
     * 
     * @param maxElastic
     */
    public void setMaxElastic(float maxElastic)
    {
        this.maxElastic = maxElastic;
    }

    /***
     * 设置拉动和松开状态切换的临界值,1-5之间
     * 
     * @param critical
     */
    public void setCritical(int critical)
    {
        this.critical = critical;
    }

}

  调用Activity:

public class BshSOViewActivity extends Activity {
    BshElasticView ev;
    BshSOGridView gv;
    GridAdagper ga = new GridAdagper();
    
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.elastic_grid );
        ev = ( BshElasticView ) findViewById( R.id.ev );
        //拉动幅度
        ev.setFactor( 2 );
        //拉动范围
        ev.setMaxElastic( 0.9f );
        gv = new BshSOGridView( this );
        gv.setBackgroundColor( Color.WHITE );
        gv.setNumColumns( 4 );
        gv.setAdapter( ga );
        ev.setScrollOverable( gv );
        ev.irefresh = new IRefresh()
        {
            @Override
            public boolean refreshTop()
            {
                new Thread( new Runnable()
                {
                    @Override
                    public void run()
                    {
                        try
                        {
                            Log.d( "bsh", "refreshing" );
                            //在这里做刷新操作读数据神马的。这里用睡觉代替
                            Thread.sleep( 3000 );

                        } catch ( InterruptedException e )
                        {
                            e.printStackTrace();
                        }
                        ev.onRefreshComplete();

                    }
                } ).start();
                return false;
            }

            @Override
            public boolean refreshBtm()
            {
                new Thread( new Runnable()
                {
                    @Override
                    public void run()
                    {
                        try
                        {
                            Log.d( "bsh", "refreshing" );
                            Thread.sleep( 3000 );
                        } catch ( InterruptedException e )
                        {
                            e.printStackTrace();
                        }
                        ev.onRefreshComplete();
                    }
                } ).start();
                return false;
            }
        };
    }

    class GridAdagper extends BaseAdapter
    {

        @Override
        public int getCount()
        {
            return 100;
        }

        @Override
        public Object getItem(int arg0)
        {
            return null;
        }

        @Override
        public long getItemId(int arg0)
        {
            return 0;
        }

        @Override
        public View getView(int arg0, View arg1, ViewGroup arg2)
        {
            if ( null == arg1 )
            {
                arg1 = new ImageView( BshSOViewActivity.this );
                arg1.setBackgroundResource( R.drawable.ic_launcher );
            }
            return arg1;
        }

    }
}

工程源码:点我

原文地址:https://www.cnblogs.com/bausch/p/2546221.html