做安卓开发的朋友们,不知道你们会不会经常用遇到这样的情景,某个做所畏的产品设计的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; } } }
工程源码:点我