在上一篇文章《Android自己定义组件系列【3】——自己定义ViewGroup实现側滑》中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果。
1、布局示意图:
2、核心代码
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度 mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度 if(!isLocked){ initX = getScrollX(); isLocked = true; } }
在该方法中获取到初始的视图坐标偏移量getScrollX()
@Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("ACTION_DOWN"); mDownX = x; //记录按下时的x坐标 break; case MotionEvent.ACTION_UP: System.out.println("ACTION_UP"); int dis = (int) (x - mDownX); //滑动的距离 if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){ if(dis > 0){ //假设>0则是向右滑动 toRightMove(); }else{ //假设<0则是向左滑动 toLeftMove(); } } break; default: break; } return true; }
监听函数记录下按下和移动的屏幕坐标。求差计算出移动距离,假设这个距离大于阀值 (mWidth * mMenuWeight / 2)则滑动
public void toRightMove(){ System.out.println("maxRight = " + maxRight); System.out.println("X = " + getScrollX()); if(getScrollX() >= initX){ int dx = (int)(mWidth * mMenuWeight); mScroller.startScroll(getScrollX(), 0, -dx, 0, 500); if(mListener != null){ mListener.onChanged(); } invalidate(); } }
假设是向右滑动则。假设当前是初始位置(centerView在中间)则能够向右滑动(getScrollX == initX),或者当前左边View能够看见,则能够向右滑动将centerView移动到中间(getScrollX > initX).同理有向左滑动的方法。
public void toLeftMove(){ System.out.println("maxLeft = " + maxLeft); System.out.println("X = " + getScrollX()); if(getScrollX() <= initX){ int dx = (int)(mWidth * mMenuWeight); mScroller.startScroll(getScrollX(), 0, dx, 0, 500); if(mListener != null){ mListener.onChanged(); } invalidate(); } }
3、所有代码
MyScrollView.java
package com.example.testscrollto; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Scroller; public class MyScrollView extends LinearLayout{ private Context mContext; private int mWidth; private int mHeight; private float mMenuWeight = 3.0f / 5; //菜单界面比例 private View mMenuView; //菜单界面 private View mPriView; //内容界面 private View mRightView; //右边界面 private int maxLeft; private int maxRight; private int initX; private boolean isLocked = false; private Scroller mScroller; private OnMenuChangedListener mListener; public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mScroller = new Scroller(mContext); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight); mPriView.layout(0, 0, mWidth, mHeight); mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMenuWeight), mHeight); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度 mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度 if(!isLocked){ initX = getScrollX(); isLocked = true; } } /**设置右滑的菜单View*/ public void setMenu(View menu){ mMenuView = menu; addView(mMenuView); } /** * 设置主界面View */ public void setPrimary(View primary){ mPriView = primary; addView(mPriView); } public void setRightView(View rightview){ mRightView = rightview; addView(mRightView); } private float mDownX; @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("ACTION_DOWN"); mDownX = x; //记录按下时的x坐标 break; case MotionEvent.ACTION_UP: System.out.println("ACTION_UP"); int dis = (int) (x - mDownX); //滑动的距离 if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){ if(dis > 0){ //假设>0则是向右滑动 toRightMove(); }else{ //假设<0则是向左滑动 toLeftMove(); } } break; default: break; } return true; } @Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } public void toRightMove(){ System.out.println("maxRight = " + maxRight); System.out.println("X = " + getScrollX()); if(getScrollX() >= initX){ int dx = (int)(mWidth * mMenuWeight); mScroller.startScroll(getScrollX(), 0, -dx, 0, 500); if(mListener != null){ mListener.onChanged(); } invalidate(); } } public void toLeftMove(){ System.out.println("maxLeft = " + maxLeft); System.out.println("X = " + getScrollX()); if(getScrollX() <= initX){ int dx = (int)(mWidth * mMenuWeight); mScroller.startScroll(getScrollX(), 0, dx, 0, 500); if(mListener != null){ mListener.onChanged(); } invalidate(); } } public interface OnMenuChangedListener{ public void onChanged(); } public void setOnMenuChangedListener(OnMenuChangedListener listener){ mListener = listener; } }
MainActivity.java
package com.example.testscrollto; import android.app.Activity; import android.os.Bundle; import android.view.View; import com.example.testscrollto.MyScrollView.OnMenuChangedListener; public class MainActivity extends Activity { private MyScrollView mScrollView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mScrollView = (MyScrollView)findViewById(R.id.rightscrollview); final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null); final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null); final View rightview = getLayoutInflater().inflate(R.layout.rightscrollview_right_menu, null); mScrollView.setMenu(menu); mScrollView.setPrimary(primary); mScrollView.setRightView(rightview); mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() { @Override public void onChanged() { System.out.println("窗体切换了一次"); } }); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.testscrollto.MyScrollView android:id="@+id/rightscrollview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
其余三个视图界面无限制。能够自由定义,这里就不贴出来了。
4、执行效果:
源码下载:http://download.csdn.net/detail/lxq_xsyu/7232701
这样就能够实现左右滑动了,没有不论什么bug吗?
事实上这样看似是没有什么问题了,上面用于推断界面位置的代码在逻辑上看似是对的,可是在实际的应用中偶尔会出现错乱。
以下我们用第二种方式解决:
package com.example.jaohangui.view; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.Scroller; public class MyScrollLeftRightView extends LinearLayout{ private Scroller mScroller; private View mLeftView; //坐标界面 private View mMainView; //中间主界面 private View mRightView; //右边界面 private float mMeasureWight = 3.0f / 5; //菜单界面比例 private int mWidth; private int mHeight; private boolean isLocked = false; private boolean isToLeft = false; private static int CENTER_PAGE = 1; private static int LEFT_PAGE = 0; private static int RIGHT_PAGE = 2; private int currentPage = CENTER_PAGE; public MyScrollLeftRightView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mLeftView.layout(-(int)(mWidth * mMeasureWight), 0, 0, mHeight); mMainView.layout(0, 0, mWidth, mHeight); mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMeasureWight), mHeight); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); mHeight = MeasureSpec.getSize(heightMeasureSpec); } /** * 加入左边界面内容 * @param view */ public void setLeftView(View view){ mLeftView = view; addView(mLeftView); } /** * 加入主界面内容 * @param view */ public void setMainView(View view){ mMainView = view; addView(mMainView); } /** * 加入右边界面内容 * @param view */ public void setRightView(View view){ mRightView = view; addView(mRightView); } private float mDownX; @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = x; break; case MotionEvent.ACTION_UP: int dis = (int)(x - mDownX); //滑动的距离 if(Math.abs(dis) > (mWidth * mMeasureWight / 3)){ if(dis > 0){ toRightMove(); }else{ toLeftMove(); } } break; default: break; } return true; } @Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ isLocked = true; scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); }else{ if(currentPage == CENTER_PAGE){ if(isToLeft){ currentPage = RIGHT_PAGE; }else{ currentPage = LEFT_PAGE; } }else{ currentPage = CENTER_PAGE; } isLocked = false; } } public void toRightMove(){ if(currentPage == LEFT_PAGE || isLocked){ return; } int dx = (int)(mWidth * mMeasureWight); mScroller.startScroll(getScrollX(), 0, -dx, 0, 500); invalidate(); isToLeft = false; } public void toLeftMove(){ if(currentPage == RIGHT_PAGE || isLocked){ return; } System.out.println("ok"); int dx = (int)(mWidth * mMeasureWight); mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); isToLeft = true; } }
上面使用了两个用来推断的变量和一个锁定状态(不让进入toLeftMove和toRightMove)的变量。
1、在进入toLeftMove或者toRightMove方法的时候首先会推断是否isLocked为true,假设为true则说明当前是正在滑动状态,不能够执行这两个方法。假设不这样去控制。在界面正在滑动的时候上面的currentPage就会发生错乱。
2、将要滑动之前告诉computeScroll()方法,是从toLeftMove和toRightMove的那个方法中出来的(使用isToLeft)。
3、最后推断Scroller是否已经停止滑动(移动),假设停止则改变当前页面的状态(currentPage的值)
有的时候我们尽管实现了一个功能或者某个逻辑,而怎样才干使这段代码更加健壮和有效更值得我们去认真思考。
。。