做安卓开发的朋友们,不知道你们会不会经常用遇到这样的情景,某个做所畏的产品设计的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 &#61; 0;final int PULL &#61; 1;final int REFRESHING_H &#61; 2;final int PUSH &#61; 3;final int RELEASE_F &#61; 4;final int REFRESHING_F &#61; 5;final int NORMAL &#61; 6;int factor &#61; 3;float maxElastic &#61; 0.2f;int critical &#61; 2;int maxElasticH, maxElasticF;boolean isTopRecored;boolean isBtmRecored &#61; false;int startY;IRefresh irefresh;RotateAnimation animation;RotateAnimation reverseAnimation;int state &#61; 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 &#61; new Handler(){&#64;Overridepublic void handleMessage(Message msg){switch ( msg.what ){case 0:Log.d( "bsh", "刷新完成&#xff0c;到normal状态" );state &#61; 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 &#61; o;addView( ( View ) o );}public void init(Context ctx){this.ctx &#61; ctx;rv &#61; View.inflate( ctx, R.layout.elastic_view, null );main_continer &#61; ( RelativeLayout ) rv.findViewById( R.id.main_continer );headerView &#61; ( RelativeLayout ) rv.findViewById( R.id.header );hPro &#61; ( ProgressBar ) rv.findViewById( R.id.pro_h );hArow &#61; ( ImageView ) rv.findViewById( R.id.iv_harow );tvTipH &#61; ( TextView ) rv.findViewById( R.id.tv_htip );tvLstH &#61; ( TextView ) rv.findViewById( R.id.tv_lst );measureViewHeight( headerView );headerH &#61; headerView.getMeasuredHeight();headerW &#61; headerView.getMeasuredWidth();footerView &#61; ( RelativeLayout ) rv.findViewById( R.id.footer );fPro &#61; ( ProgressBar ) rv.findViewById( R.id.pro_f );fArow &#61; ( ImageView ) rv.findViewById( R.id.iv_farow );tvTipF &#61; ( TextView ) rv.findViewById( R.id.tv_ftip );tvLstF &#61; ( TextView ) rv.findViewById( R.id.tv_lstf );footerH &#61; headerH;footerW &#61; headerW;continer &#61; ( 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 &#61; new RotateAnimation( 0, -180,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f );animation.setDuration( 500 );animation.setFillAfter( true );reverseAnimation &#61; new RotateAnimation( -180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f );reverseAnimation.setDuration( 500 );reverseAnimation.setFillAfter( true );state &#61; NORMAL;}private void measureViewHeight(View child){ViewGroup.LayoutParams p &#61; child.getLayoutParams();if ( null &#61;&#61; p ){p &#61; new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.FILL_PARENT );}int childHeightSpec &#61; ViewGroup.getChildMeasureSpec( 0, 0 &#43; 0, p.height );int lpWidth &#61; p.width;int childWidthSpec;if ( lpWidth > 0 ){childWidthSpec &#61; MeasureSpec.makeMeasureSpec( lpWidth,MeasureSpec.EXACTLY );}else{childWidthSpec &#61; MeasureSpec.makeMeasureSpec( 0,MeasureSpec.UNSPECIFIED );}child.measure( childWidthSpec, childHeightSpec );}&#64;Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b){super.onLayout( changed, l, t, r, b );rvH &#61; b;rvW &#61; r;footerH &#61; headerH;footerW &#61; headerW;continerH &#61; rvH;continerW &#61; rvW;maxElasticH &#61; ( int ) (headerH &#43; continerH * maxElastic);maxElasticF &#61; 0 - maxElasticH;RelativeLayout.LayoutParams rl &#61; 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", "当前状态&#xff0c;松开刷新" );break;case PULL:hPro.setVisibility( View.GONE );tvTipH.setVisibility( View.VISIBLE );tvLstH.setVisibility( View.VISIBLE );hArow.clearAnimation();hArow.setVisibility( View.VISIBLE );if ( isBack ){isBack &#61; false;hArow.clearAnimation();hArow.startAnimation( reverseAnimation );}tvTipH.setText( "下拉刷新" );Log.d( "bsh", "当前状态&#xff0c;上推刷新" );break;case REFRESHING_H:hPro.setVisibility( View.VISIBLE );hArow.clearAnimation();hArow.setVisibility( View.GONE );tvTipH.setText( "正在刷新..." );tvLstH.setVisibility( View.INVISIBLE );tvLstH.setText( "上次更新&#xff1a;"&#43; 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", "当前状态&#xff0c;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", "当前状态&#xff0c;松开刷新" );break;case PUSH:fPro.setVisibility( View.GONE );tvTipF.setVisibility( View.VISIBLE );tvLstF.setVisibility( View.VISIBLE );fArow.clearAnimation();fArow.setVisibility( View.VISIBLE );if ( isBack ){isBack &#61; false;fArow.clearAnimation();fArow.startAnimation( reverseAnimation );}tvTipF.setText( "上推刷新" );Log.d( "bsh", "当前状态&#xff0c;上推刷新" );break;case REFRESHING_F:fPro.setVisibility( View.VISIBLE );fArow.clearAnimation();fArow.setVisibility( View.GONE );tvTipF.setText( "正在刷新..." );tvLstF.setVisibility( View.INVISIBLE );tvLstF.setText( "上次更新&#xff1a;"&#43; 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", "当前状态&#xff0c;normalf" );break;}}public void setRvPadding(int padding){Log.d( "bsh", "padding:" &#43; padding );if ( padding < maxElasticF ){return;}else if ( padding > maxElasticH ){return;}main_continer.setPadding( 0, padding, 0, 0 );rv.requestLayout();}&#64;Overridepublic 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 &#61;&#61; NORMAL || state &#61;&#61; REFRESHING_H || state &#61;&#61; REFRESHING_F ){return super.dispatchTouchEvent( event );}elsereturn true;}public void handleMove(MotionEvent event){int tempY &#61; ( int ) event.getY();tempY &#61; tempY <0 ? 0 : tempY;Log.d( "bsh", "get temp:" &#43; tempY );startRecord( event );if ( state !&#61; REFRESHING_F && isBtmRecored ){handleMoveF( tempY );}else if ( state !&#61; REFRESHING_H && isTopRecored ){handleMoveH( tempY );}}public void handleMoveH(int tempY){Log.d( "bsh", "release to refresh h" );if ( state &#61;&#61; RELEASE_H ){Log.d( "bsh", "release to refresh h" );if ( ((tempY - startY) / factor critical)&& (tempY - startY) > 0 ){state &#61; PULL;changeHeaderViewByState();Log.d( "bsh", "由松开刷新状态转变到下拉刷新状态" );}else if ( startY - tempY > 0 ){state &#61; NORMAL;changeHeaderViewByState();Log.d( "bsh", "由松开刷新状态转变到normal状态" );}}if ( state &#61;&#61; PULL ){Log.d( "bsh", "push to refresh" );if ( (tempY - startY) / factor >&#61; headerH * critical ){state &#61; RELEASE_H;isBack &#61; true;changeHeaderViewByState();Log.d( "bsh", "由normal或者下拉刷新状态转变到松开刷新" );}else if ( tempY - startY <&#61; 0 ){state &#61; NORMAL;changeHeaderViewByState();Log.d( "bsh", "由normal或者下拉刷新状态转变到normal状态" );}}if ( state &#61;&#61; NORMAL ){if ( tempY - startY > 0 ){Log.d( "bsh", "normalf to push to refersh" );state &#61; PULL;changeHeaderViewByState();}}// 这里就是处理弹性效果的地方if ( state &#61;&#61; PULL || state &#61;&#61; RELEASE_H ){setRvPadding( (tempY - startY) / factor - headerH );}}public void handleMoveF(int tempY){if ( state &#61;&#61; RELEASE_F ){Log.d( "bsh", "release to refresh f" );if ( ((tempY - startY) / factor > 0 - headerH * critical)&& (tempY - startY) <0 ){state &#61; PUSH;changeFooterViewByState();Log.d( "bsh", "由松开刷新状态转变到上推刷新状态" );}else if ( tempY - startY >&#61; 0 ){state &#61; NORMAL;changeFooterViewByState();Log.d( "bsh", "由松开刷新状态转变到normal状态" );}}if ( state &#61;&#61; PUSH ){Log.d( "bsh", "push to refresh" );if ( (tempY - startY) / factor <&#61; (0 - headerH * critical) ){state &#61; RELEASE_F;isBack &#61; true;changeFooterViewByState();Log.d( "bsh", "由normal或者上推刷新状态转变到松开刷新" );}else if ( tempY - startY >&#61; 0 ){state &#61; NORMAL;changeFooterViewByState();Log.d( "bsh", "由normal或者上推刷新状态转变到normal状态" );}}if ( state &#61;&#61; NORMAL ){if ( tempY - startY <0 ){Log.d( "bsh", "normalf to push to refersh" );state &#61; PUSH;changeFooterViewByState();}}// 这里就是处理弹性效果的地方。if ( state &#61;&#61; PUSH || state &#61;&#61; RELEASE_F ){setRvPadding( (tempY - startY) / factor - headerH * 2 );}}public void startRecord(MotionEvent event){if ( overable.isScrollOnTop() && !isTopRecored ){isTopRecored &#61; true;startY &#61; ( int ) event.getY();}if ( overable.isScrollOnBtm() && !isBtmRecored ){isBtmRecored &#61; true;startY &#61; ( int ) event.getY();}}public void handleUpF(){if ( state !&#61; REFRESHING_F ){if ( state &#61;&#61; PUSH ){state &#61; NORMAL;changeFooterViewByState();Log.d( "bsh", "由上推刷新状态&#xff0c;到normal状态" );}if ( state &#61;&#61; RELEASE_F ){state &#61; REFRESHING_F;changeFooterViewByState();setRvPadding( -1 * headerH - 1 );irefresh.refreshBtm();}}isBtmRecored &#61; false;isBack &#61; false;}public void handleUpH(){if ( state !&#61; REFRESHING_H ){if ( state &#61;&#61; PULL ){state &#61; NORMAL;changeHeaderViewByState();Log.d( "bsh", "由下拉状态&#xff0c;到normal状态" );}if ( state &#61;&#61; RELEASE_H ){state &#61; REFRESHING_H;changeHeaderViewByState();setRvPadding( 0 );irefresh.refreshTop();}}isTopRecored &#61; false;isBack &#61; false;}public void onRefreshComplete(){handler.sendEmptyMessage( 0 );}public interface IRefresh{public boolean refreshTop();public boolean refreshBtm();}/**** 设置拉动效果幅度建议值&#xff08;1-5&#xff09;* * &#64;param factor*/public void setFactor(int factor){this.factor &#61; factor;}/**** 设置最大拉动的位置&#xff0c;请在&#xff08;0.0f-1.0f&#xff09;之间取值* * &#64;param maxElastic*/public void setMaxElastic(float maxElastic){this.maxElastic &#61; maxElastic;}/**** 设置拉动和松开状态切换的临界值&#xff0c;1-5之间* * &#64;param critical*/public void setCritical(int critical){this.critical &#61; critical;}}
调用Activity:
public class BshSOViewActivity extends Activity {BshElasticView ev;BshSOGridView gv;GridAdagper ga &#61; new GridAdagper();&#64;Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate( savedInstanceState );setContentView( R.layout.elastic_grid );ev &#61; ( BshElasticView ) findViewById( R.id.ev );//拉动幅度ev.setFactor( 2 );//拉动范围ev.setMaxElastic( 0.9f );gv &#61; new BshSOGridView( this );gv.setBackgroundColor( Color.WHITE );gv.setNumColumns( 4 );gv.setAdapter( ga );ev.setScrollOverable( gv );ev.irefresh &#61; new IRefresh(){&#64;Overridepublic boolean refreshTop(){new Thread( new Runnable(){&#64;Overridepublic void run(){try{Log.d( "bsh", "refreshing" );//在这里做刷新操作读数据神马的。这里用睡觉代替Thread.sleep( 3000 );} catch ( InterruptedException e ){e.printStackTrace();}ev.onRefreshComplete();}} ).start();return false;}&#64;Overridepublic boolean refreshBtm(){new Thread( new Runnable(){&#64;Overridepublic void run(){try{Log.d( "bsh", "refreshing" );Thread.sleep( 3000 );} catch ( InterruptedException e ){e.printStackTrace();}ev.onRefreshComplete();}} ).start();return false;}};}class GridAdagper extends BaseAdapter{&#64;Overridepublic int getCount(){return 100;}&#64;Overridepublic Object getItem(int arg0){return null;}&#64;Overridepublic long getItemId(int arg0){return 0;}&#64;Overridepublic View getView(int arg0, View arg1, ViewGroup arg2){if ( null &#61;&#61; arg1 ){arg1 &#61; new ImageView( BshSOViewActivity.this );arg1.setBackgroundResource( R.drawable.ic_launcher );}return arg1;}}
}
工程源码&#xff1a;点我