Android 3D滑动菜单完全解析,实现推拉门式的立体特效

2023-10-27



转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/10471245

在上一篇文章中,我们学习了Camera的基本用法,并借助它们编写了一个例子,实现了类似于API Demos里的图片中轴旋转功能。不过那个例子的核心代码是来自于API Demos中带有的Rotate3dAnimation这个类,是它帮助我们完成了所有的三维旋转操作,所有Matrix和Camera相关的代码也是封装在这个类中。

这样说来的话,大家心里会不会痒痒的呢?虽然学习了Camera的用法,但却没有按照自己的理解来实现一套非常炫酷的3D效果。不要着急,今天我就带着大家一起来实现一种3D推拉门式的滑动菜单,而且完全不会借助任何API Demos里面的代码。

当然如果你还不是很了解Camera的使用方式,可以先去阅读我的上一篇文章 Android中轴旋转特效实现,制作别样的图片浏览器 。

关于滑动菜单的文章我也已经写过好几篇了,相信看过的朋友对滑动菜单的实现方式应该都已经比较熟悉了,那么本篇文章的重点就在于,如何在传统滑动菜单的基础上加入推拉门式的立体效果。还不了解滑动菜单如何实现的朋友,可以去翻一翻我之前的文章。说到这里我必须要吐槽一下了,最近发现有不少的网站和个人将我的文章恶意转走,而且还特意把第一行的原文地址信息去除掉。更可气的是,在百度上搜索我文章的标题时,竟然先找到的是那些转载我文章的网站。唉,伤心了,看来还是谷歌比较正常。因此今天我也是在这里特别申明一下,我所写的所有文章均是首发于CSDN博客,如果你阅读这篇文章时是在别的网站,那么你将无法找到我前面所写的关于传统滑动菜单的文章,而且你的疑问和留言也将得不到解答。

下面还是回到正题,首先来讲一下这次的实现原理吧,其实传统的滑动菜单功能就是把菜单部分放在了下面,主布局放在了上面,然后根据手指滑动的距离来偏移主布局,让菜单部分得以显示出来就行了。不过我们这次既然要做推拉门式的立体效果,就需要将传统的思维稍微转变一下,可以先让菜单部分隐藏掉,但却复制一个菜单的镜像并生成一张图片,然后在手指滑动的时候对这张图片进行三维操作,让它产生推拉门式的效果,等滑动操作结束的时候,才让真正的菜单显示出来,然后将这个图片隐藏。原理示意图如下所示:

                                      

那么下面我们就开始动手实现吧,首先新建一个Android项目,起名叫做ThreeDSlidingLayoutDemo。

然后新建一个Image3dView类继承自View,用于生成镜像图片,以及完成三维操作,代码如下所示:

  1. public class Image3dView extends View {  
  2.   
  3.     /** 
  4.      * 源视图,用于生成图片对象。 
  5.      */  
  6.     private View sourceView;  
  7.   
  8.     /** 
  9.      * 根据传入的源视图生成的图片对象。 
  10.      */  
  11.     private Bitmap sourceBitmap;  
  12.   
  13.     /** 
  14.      * 源视图的宽度。 
  15.      */  
  16.     private float sourceWidth;  
  17.   
  18.     /** 
  19.      * Matrix对象,用于对图片进行矩阵操作。 
  20.      */  
  21.     private Matrix matrix = new Matrix();  
  22.   
  23.     /** 
  24.      * Camera对象,用于对图片进行三维操作。 
  25.      */  
  26.     private Camera camera = new Camera();  
  27.   
  28.     /** 
  29.      * Image3dView的构造函数 
  30.      *  
  31.      * @param context 
  32.      * @param attrs 
  33.      */  
  34.     public Image3dView(Context context, AttributeSet attrs) {  
  35.         super(context, attrs);  
  36.     }  
  37.   
  38.     /** 
  39.      * 提供外部接口,允许向Image3dView传入源视图。 
  40.      *  
  41.      * @param view 
  42.      *            传入的源视图 
  43.      */  
  44.     public void setSourceView(View view) {  
  45.         sourceView = view;  
  46.         sourceWidth = sourceView.getWidth();  
  47.     }  
  48.   
  49.     /** 
  50.      * 清除掉缓存的图片对象。 
  51.      */  
  52.     public void clearSourceBitmap() {  
  53.         if (sourceBitmap != null) {  
  54.             sourceBitmap = null;  
  55.         }  
  56.     }  
  57.   
  58.     @Override  
  59.     protected void onDraw(Canvas canvas) {  
  60.         super.onDraw(canvas);  
  61.         if (sourceBitmap == null) {  
  62.             getSourceBitmap();  
  63.         }  
  64.         // 计算图片需要旋转的角度  
  65.         float degree = 90 - (90 / sourceWidth) * getWidth();  
  66.         camera.save();  
  67.         camera.rotateY(degree);  
  68.         camera.getMatrix(matrix);  
  69.         camera.restore();  
  70.         // 将旋转的中心点移动到屏幕左边缘的中间位置  
  71.         matrix.preTranslate(0, -getHeight() / 2);  
  72.         matrix.postTranslate(0, getHeight() / 2);  
  73.         canvas.drawBitmap(sourceBitmap, matrix, null);  
  74.     }  
  75.   
  76.     /** 
  77.      * 获取源视图对应的图片对象。 
  78.      */  
  79.     private void getSourceBitmap() {  
  80.         if (sourceView != null) {  
  81.             sourceView.setDrawingCacheEnabled(true);  
  82.             sourceView.layout(00, sourceView.getWidth(), sourceView.getHeight());  
  83.             sourceView.buildDrawingCache();  
  84.             sourceBitmap = sourceView.getDrawingCache();  
  85.         }  
  86.     }  
  87.   
  88. }  
public class Image3dView extends View {

	/**
	 * 源视图,用于生成图片对象。
	 */
	private View sourceView;

	/**
	 * 根据传入的源视图生成的图片对象。
	 */
	private Bitmap sourceBitmap;

	/**
	 * 源视图的宽度。
	 */
	private float sourceWidth;

	/**
	 * Matrix对象,用于对图片进行矩阵操作。
	 */
	private Matrix matrix = new Matrix();

	/**
	 * Camera对象,用于对图片进行三维操作。
	 */
	private Camera camera = new Camera();

	/**
	 * Image3dView的构造函数
	 * 
	 * @param context
	 * @param attrs
	 */
	public Image3dView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	/**
	 * 提供外部接口,允许向Image3dView传入源视图。
	 * 
	 * @param view
	 *            传入的源视图
	 */
	public void setSourceView(View view) {
		sourceView = view;
		sourceWidth = sourceView.getWidth();
	}

	/**
	 * 清除掉缓存的图片对象。
	 */
	public void clearSourceBitmap() {
		if (sourceBitmap != null) {
			sourceBitmap = null;
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if (sourceBitmap == null) {
			getSourceBitmap();
		}
		// 计算图片需要旋转的角度
		float degree = 90 - (90 / sourceWidth) * getWidth();
		camera.save();
		camera.rotateY(degree);
		camera.getMatrix(matrix);
		camera.restore();
		// 将旋转的中心点移动到屏幕左边缘的中间位置
		matrix.preTranslate(0, -getHeight() / 2);
		matrix.postTranslate(0, getHeight() / 2);
		canvas.drawBitmap(sourceBitmap, matrix, null);
	}

	/**
	 * 获取源视图对应的图片对象。
	 */
	private void getSourceBitmap() {
		if (sourceView != null) {
			sourceView.setDrawingCacheEnabled(true);
			sourceView.layout(0, 0, sourceView.getWidth(), sourceView.getHeight());
			sourceView.buildDrawingCache();
			sourceBitmap = sourceView.getDrawingCache();
		}
	}

}
可以看到,Image3dView中提供了一个setSourceView()方法,用于传递源视图进来,我们稍后复制镜像就是对它进行复制。然后在onDraw()方法里对sourceBitmap进行判断,如果为空,则去调用getSourceBitmap()方法来生成一张镜像图片,getSourceBitmap()方法的细节大家自己去看。在获得了镜像图片之后,接下来就是要计算图片的旋转角度了,这里根据Image3dView当前的宽度和源视图的总宽度进行对比,按比例算出旋转的角度。然后调用Camera的rotateY()方法,让图片团练Y轴进行旋转,并将旋转的中心点移动到屏幕左边缘的中间位置,这几行代码我们在上篇文章中已经见过了,算是挺熟悉了吧!最后调用Canvas的drawBitmap()方法把图片绘制出来。

完成了Image3dView之后,接着我们要开始编写滑动菜单部分的代码,其实这次的代码和之前的滑动菜单代码大同小异,看过我前面文章的朋友,这次理解起来一定会轻而易举。新建ThreeDSlidingLayout类,代码如下所示:

  1. public class ThreeDSlidingLayout extends RelativeLayout implements OnTouchListener {  
  2.   
  3.     /** 
  4.      * 滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。 
  5.      */  
  6.     public static final int SNAP_VELOCITY = 200;  
  7.   
  8.     /** 
  9.      * 滑动状态的一种,表示未进行任何滑动。 
  10.      */  
  11.     public static final int DO_NOTHING = 0;  
  12.   
  13.     /** 
  14.      * 滑动状态的一种,表示正在滑出左侧菜单。 
  15.      */  
  16.     public static final int SHOW_MENU = 1;  
  17.   
  18.     /** 
  19.      * 滑动状态的一种,表示正在隐藏左侧菜单。 
  20.      */  
  21.     public static final int HIDE_MENU = 2;  
  22.   
  23.     /** 
  24.      * 记录当前的滑动状态 
  25.      */  
  26.     private int slideState;  
  27.   
  28.     /** 
  29.      * 屏幕宽度值。 
  30.      */  
  31.     private int screenWidth;  
  32.   
  33.     /** 
  34.      * 右侧布局最多可以滑动到的左边缘。 
  35.      */  
  36.     private int leftEdge = 0;  
  37.   
  38.     /** 
  39.      * 右侧布局最多可以滑动到的右边缘。 
  40.      */  
  41.     private int rightEdge = 0;  
  42.   
  43.     /** 
  44.      * 在被判定为滚动之前用户手指可以移动的最大值。 
  45.      */  
  46.     private int touchSlop;  
  47.   
  48.     /** 
  49.      * 记录手指按下时的横坐标。 
  50.      */  
  51.     private float xDown;  
  52.   
  53.     /** 
  54.      * 记录手指按下时的纵坐标。 
  55.      */  
  56.     private float yDown;  
  57.   
  58.     /** 
  59.      * 记录手指移动时的横坐标。 
  60.      */  
  61.     private float xMove;  
  62.   
  63.     /** 
  64.      * 记录手指移动时的纵坐标。 
  65.      */  
  66.     private float yMove;  
  67.   
  68.     /** 
  69.      * 记录手机抬起时的横坐标。 
  70.      */  
  71.     private float xUp;  
  72.   
  73.     /** 
  74.      * 左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。 
  75.      */  
  76.     private boolean isLeftLayoutVisible;  
  77.   
  78.     /** 
  79.      * 是否正在滑动。 
  80.      */  
  81.     private boolean isSliding;  
  82.   
  83.     /** 
  84.      * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次 
  85.      */  
  86.     private boolean loadOnce;  
  87.   
  88.     /** 
  89.      * 左侧布局对象。 
  90.      */  
  91.     private View leftLayout;  
  92.   
  93.     /** 
  94.      * 右侧布局对象。 
  95.      */  
  96.     private View rightLayout;  
  97.   
  98.     /** 
  99.      * 在滑动过程中展示的3D视图 
  100.      */  
  101.     private Image3dView image3dView;  
  102.   
  103.     /** 
  104.      * 用于监听侧滑事件的View。 
  105.      */  
  106.     private View mBindView;  
  107.   
  108.     /** 
  109.      * 左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。 
  110.      */  
  111.     private MarginLayoutParams leftLayoutParams;  
  112.   
  113.     /** 
  114.      * 右侧布局的参数,通过此参数来重新确定右侧布局的宽度。 
  115.      */  
  116.     private MarginLayoutParams rightLayoutParams;  
  117.   
  118.     /** 
  119.      * 3D视图的参数,通过此参数来重新确定3D视图的宽度。 
  120.      */  
  121.     private ViewGroup.LayoutParams image3dViewParams;  
  122.   
  123.     /** 
  124.      * 用于计算手指滑动的速度。 
  125.      */  
  126.     private VelocityTracker mVelocityTracker;  
  127.   
  128.     /** 
  129.      * 重写SlidingLayout的构造函数,其中获取了屏幕的宽度。 
  130.      *  
  131.      * @param context 
  132.      * @param attrs 
  133.      */  
  134.     public ThreeDSlidingLayout(Context context, AttributeSet attrs) {  
  135.         super(context, attrs);  
  136.         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
  137.         screenWidth = wm.getDefaultDisplay().getWidth();  
  138.         touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  139.     }  
  140.   
  141.     /** 
  142.      * 绑定监听侧滑事件的View,即在绑定的View进行滑动才可以显示和隐藏左侧布局。 
  143.      *  
  144.      * @param bindView 
  145.      *            需要绑定的View对象。 
  146.      */  
  147.     public void setScrollEvent(View bindView) {  
  148.         mBindView = bindView;  
  149.         mBindView.setOnTouchListener(this);  
  150.     }  
  151.   
  152.     /** 
  153.      * 将屏幕滚动到左侧布局界面,滚动速度设定为10. 
  154.      */  
  155.     public void scrollToLeftLayout() {  
  156.         image3dView.clearSourceBitmap();  
  157.         new ScrollTask().execute(-10);  
  158.     }  
  159.   
  160.     /** 
  161.      * 将屏幕滚动到右侧布局界面,滚动速度设定为-10. 
  162.      */  
  163.     public void scrollToRightLayout() {  
  164.         image3dView.clearSourceBitmap();  
  165.         new ScrollTask().execute(10);  
  166.     }  
  167.   
  168.     /** 
  169.      * 左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。 
  170.      *  
  171.      * @return 左侧布局完全显示返回true,完全隐藏返回false。 
  172.      */  
  173.     public boolean isLeftLayoutVisible() {  
  174.         return isLeftLayoutVisible;  
  175.     }  
  176.   
  177.     /** 
  178.      * 在onLayout中重新设定左侧布局和右侧布局的参数。 
  179.      */  
  180.     @Override  
  181.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  182.         super.onLayout(changed, l, t, r, b);  
  183.         if (changed && !loadOnce) {  
  184.             // 获取左侧布局对象  
  185.             leftLayout = findViewById(R.id.menu);  
  186.             leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();  
  187.             rightEdge = -leftLayoutParams.width;  
  188.             // 获取右侧布局对象  
  189.             rightLayout = findViewById(R.id.content);  
  190.             rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();  
  191.             rightLayoutParams.width = screenWidth;  
  192.             rightLayout.setLayoutParams(rightLayoutParams);  
  193.             // 获取3D视图对象  
  194.             image3dView = (Image3dView) findViewById(R.id.image_3d_view);  
  195.             // 将左侧布局传入3D视图中作为生成源  
  196.             image3dView.setSourceView(leftLayout);  
  197.             loadOnce = true;  
  198.         }  
  199.     }  
  200.   
  201.     @Override  
  202.     public boolean onTouch(View v, MotionEvent event) {  
  203.         createVelocityTracker(event);  
  204.         switch (event.getAction()) {  
  205.         case MotionEvent.ACTION_DOWN:  
  206.             // 手指按下时,记录按下时的横坐标  
  207.             xDown = event.getRawX();  
  208.             yDown = event.getRawY();  
  209.             slideState = DO_NOTHING;  
  210.             break;  
  211.         case MotionEvent.ACTION_MOVE:  
  212.             // 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局  
  213.             xMove = event.getRawX();  
  214.             yMove = event.getRawY();  
  215.             int moveDistanceX = (int) (xMove - xDown);  
  216.             int moveDistanceY = (int) (yMove - yDown);  
  217.             checkSlideState(moveDistanceX, moveDistanceY);  
  218.             switch (slideState) {  
  219.             case SHOW_MENU:  
  220.                 rightLayoutParams.rightMargin = -moveDistanceX;  
  221.                 onSlide();  
  222.                 break;  
  223.             case HIDE_MENU:  
  224.                 rightLayoutParams.rightMargin = rightEdge - moveDistanceX;  
  225.                 onSlide();  
  226.                 break;  
  227.             default:  
  228.                 break;  
  229.             }  
  230.             break;  
  231.         case MotionEvent.ACTION_UP:  
  232.             xUp = event.getRawX();  
  233.             int upDistanceX = (int) (xUp - xDown);  
  234.             if (isSliding) {  
  235.                 // 手指抬起时,进行判断当前手势的意图  
  236.                 switch (slideState) {  
  237.                 case SHOW_MENU:  
  238.                     if (shouldScrollToLeftLayout()) {  
  239.                         scrollToLeftLayout();  
  240.                     } else {  
  241.                         scrollToRightLayout();  
  242.                     }  
  243.                     break;  
  244.                 case HIDE_MENU:  
  245.                     if (shouldScrollToRightLayout()) {  
  246.                         scrollToRightLayout();  
  247.                     } else {  
  248.                         scrollToLeftLayout();  
  249.                     }  
  250.                     break;  
  251.                 default:  
  252.                     break;  
  253.                 }  
  254.             } else if (upDistanceX < touchSlop && isLeftLayoutVisible) {  
  255.                 scrollToRightLayout();  
  256.             }  
  257.             recycleVelocityTracker();  
  258.             break;  
  259.         }  
  260.         if (v.isEnabled()) {  
  261.             if (isSliding) {  
  262.                 unFocusBindView();  
  263.                 return true;  
  264.             }  
  265.             if (isLeftLayoutVisible) {  
  266.                 return true;  
  267.             }  
  268.             return false;  
  269.         }  
  270.         return true;  
  271.     }  
  272.   
  273.     /** 
  274.      * 执行滑动过程中的逻辑操作,如边界检查,改变偏移值,可见性检查等。 
  275.      */  
  276.     private void onSlide() {  
  277.         checkSlideBorder();  
  278.         rightLayout.setLayoutParams(rightLayoutParams);  
  279.         image3dView.clearSourceBitmap();  
  280.         image3dViewParams = image3dView.getLayoutParams();  
  281.         image3dViewParams.width = -rightLayoutParams.rightMargin;  
  282.         // 滑动的同时改变3D视图的大小  
  283.         image3dView.setLayoutParams(image3dViewParams);  
  284.         // 保证在滑动过程中3D视图可见,左侧布局不可见  
  285.         showImage3dView();  
  286.     }  
  287.   
  288.     /** 
  289.      * 根据手指移动的距离,判断当前用户的滑动意图,然后给slideState赋值成相应的滑动状态值。 
  290.      *  
  291.      * @param moveDistanceX 
  292.      *            横向移动的距离 
  293.      * @param moveDistanceY 
  294.      *            纵向移动的距离 
  295.      */  
  296.     private void checkSlideState(int moveDistanceX, int moveDistanceY) {  
  297.         if (isLeftLayoutVisible) {  
  298.             if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0) {  
  299.                 isSliding = true;  
  300.                 slideState = HIDE_MENU;  
  301.             }  
  302.         } else if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX > 0  
  303.                 && Math.abs(moveDistanceY) < touchSlop) {  
  304.             isSliding = true;  
  305.             slideState = SHOW_MENU;  
  306.         }  
  307.     }  
  308.   
  309.     /** 
  310.      * 在滑动过程中检查左侧菜单的边界值,防止绑定布局滑出屏幕。 
  311.      */  
  312.     private void checkSlideBorder() {  
  313.         if (rightLayoutParams.rightMargin > leftEdge) {  
  314.             rightLayoutParams.rightMargin = leftEdge;  
  315.         } else if (rightLayoutParams.rightMargin < rightEdge) {  
  316.             rightLayoutParams.rightMargin = rightEdge;  
  317.         }  
  318.     }  
  319.   
  320.     /** 
  321.      * 判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY, 
  322.      * 就认为应该滚动将左侧布局展示出来。 
  323.      *  
  324.      * @return 如果应该滚动将左侧布局展示出来返回true,否则返回false。 
  325.      */  
  326.     private boolean shouldScrollToLeftLayout() {  
  327.         return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;  
  328.     }  
  329.   
  330.     /** 
  331.      * 判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2, 
  332.      * 或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。 
  333.      *  
  334.      * @return 如果应该滚动将右侧布局展示出来返回true,否则返回false。 
  335.      */  
  336.     private boolean shouldScrollToRightLayout() {  
  337.         return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;  
  338.     }  
  339.   
  340.     /** 
  341.      * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。 
  342.      *  
  343.      * @param event 
  344.      *            右侧布局监听控件的滑动事件 
  345.      */  
  346.     private void createVelocityTracker(MotionEvent event) {  
  347.         if (mVelocityTracker == null) {  
  348.             mVelocityTracker = VelocityTracker.obtain();  
  349.         }  
  350.         mVelocityTracker.addMovement(event);  
  351.     }  
  352.   
  353.     /** 
  354.      * 获取手指在右侧布局的监听View上的滑动速度。 
  355.      *  
  356.      * @return 滑动速度,以每秒钟移动了多少像素值为单位。 
  357.      */  
  358.     private int getScrollVelocity() {  
  359.         mVelocityTracker.computeCurrentVelocity(1000);  
  360.         int velocity = (int) mVelocityTracker.getXVelocity();  
  361.         return Math.abs(velocity);  
  362.     }  
  363.   
  364.     /** 
  365.      * 回收VelocityTracker对象。 
  366.      */  
  367.     private void recycleVelocityTracker() {  
  368.         mVelocityTracker.recycle();  
  369.         mVelocityTracker = null;  
  370.     }  
  371.   
  372.     /** 
  373.      * 使用可以获得焦点的控件在滑动的时候失去焦点。 
  374.      */  
  375.     private void unFocusBindView() {  
  376.         if (mBindView != null) {  
  377.             mBindView.setPressed(false);  
  378.             mBindView.setFocusable(false);  
  379.             mBindView.setFocusableInTouchMode(false);  
  380.         }  
  381.     }  
  382.   
  383.     /** 
  384.      * 保证此时让左侧布局不可见,3D视图可见,从而让滑动过程中产生3D的效果。 
  385.      */  
  386.     private void showImage3dView() {  
  387.         if (image3dView.getVisibility() != View.VISIBLE) {  
  388.             image3dView.setVisibility(View.VISIBLE);  
  389.         }  
  390.         if (leftLayout.getVisibility() != View.INVISIBLE) {  
  391.             leftLayout.setVisibility(View.INVISIBLE);  
  392.         }  
  393.     }  
  394.   
  395.     class ScrollTask extends AsyncTask<Integer, Integer, Integer> {  
  396.   
  397.         @Override  
  398.         protected Integer doInBackground(Integer... speed) {  
  399.             int rightMargin = rightLayoutParams.rightMargin;  
  400.             // 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。  
  401.             while (true) {  
  402.                 rightMargin = rightMargin + speed[0];  
  403.                 if (rightMargin < rightEdge) {  
  404.                     rightMargin = rightEdge;  
  405.                     break;  
  406.                 }  
  407.                 if (rightMargin > leftEdge) {  
  408.                     rightMargin = leftEdge;  
  409.                     break;  
  410.                 }  
  411.                 publishProgress(rightMargin);  
  412.                 // 为了要有滚动效果产生,每次循环使线程睡眠5毫秒,这样肉眼才能够看到滚动动画。  
  413.                 sleep(5);  
  414.             }  
  415.             if (speed[0] > 0) {  
  416.                 isLeftLayoutVisible = false;  
  417.             } else {  
  418.                 isLeftLayoutVisible = true;  
  419.             }  
  420.             isSliding = false;  
  421.             return rightMargin;  
  422.         }  
  423.   
  424.         @Override  
  425.         protected void onProgressUpdate(Integer... rightMargin) {  
  426.             rightLayoutParams.rightMargin = rightMargin[0];  
  427.             rightLayout.setLayoutParams(rightLayoutParams);  
  428.             image3dViewParams = image3dView.getLayoutParams();  
  429.             image3dViewParams.width = -rightLayoutParams.rightMargin;  
  430.             image3dView.setLayoutParams(image3dViewParams);  
  431.             showImage3dView();  
  432.             unFocusBindView();  
  433.         }  
  434.   
  435.         @Override  
  436.         protected void onPostExecute(Integer rightMargin) {  
  437.             rightLayoutParams.rightMargin = rightMargin;  
  438.             rightLayout.setLayoutParams(rightLayoutParams);  
  439.             image3dViewParams = image3dView.getLayoutParams();  
  440.             image3dViewParams.width = -rightLayoutParams.rightMargin;  
  441.             image3dView.setLayoutParams(image3dViewParams);  
  442.             if (isLeftLayoutVisible) {  
  443.                 // 保证在滑动结束后左侧布局可见,3D视图不可见。  
  444.                 image3dView.setVisibility(View.INVISIBLE);  
  445.                 leftLayout.setVisibility(View.VISIBLE);  
  446.             }  
  447.         }  
  448.     }  
  449.   
  450.     /** 
  451.      * 使当前线程睡眠指定的毫秒数。 
  452.      *  
  453.      * @param millis 
  454.      *            指定当前线程睡眠多久,以毫秒为单位 
  455.      */  
  456.     private void sleep(long millis) {  
  457.         try {  
  458.             Thread.sleep(millis);  
  459.         } catch (InterruptedException e) {  
  460.             e.printStackTrace();  
  461.         }  
  462.     }  
  463. }  
public class ThreeDSlidingLayout extends RelativeLayout implements OnTouchListener {

	/**
	 * 滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
	 */
	public static final int SNAP_VELOCITY = 200;

	/**
	 * 滑动状态的一种,表示未进行任何滑动。
	 */
	public static final int DO_NOTHING = 0;

	/**
	 * 滑动状态的一种,表示正在滑出左侧菜单。
	 */
	public static final int SHOW_MENU = 1;

	/**
	 * 滑动状态的一种,表示正在隐藏左侧菜单。
	 */
	public static final int HIDE_MENU = 2;

	/**
	 * 记录当前的滑动状态
	 */
	private int slideState;

	/**
	 * 屏幕宽度值。
	 */
	private int screenWidth;

	/**
	 * 右侧布局最多可以滑动到的左边缘。
	 */
	private int leftEdge = 0;

	/**
	 * 右侧布局最多可以滑动到的右边缘。
	 */
	private int rightEdge = 0;

	/**
	 * 在被判定为滚动之前用户手指可以移动的最大值。
	 */
	private int touchSlop;

	/**
	 * 记录手指按下时的横坐标。
	 */
	private float xDown;

	/**
	 * 记录手指按下时的纵坐标。
	 */
	private float yDown;

	/**
	 * 记录手指移动时的横坐标。
	 */
	private float xMove;

	/**
	 * 记录手指移动时的纵坐标。
	 */
	private float yMove;

	/**
	 * 记录手机抬起时的横坐标。
	 */
	private float xUp;

	/**
	 * 左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
	 */
	private boolean isLeftLayoutVisible;

	/**
	 * 是否正在滑动。
	 */
	private boolean isSliding;

	/**
	 * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
	 */
	private boolean loadOnce;

	/**
	 * 左侧布局对象。
	 */
	private View leftLayout;

	/**
	 * 右侧布局对象。
	 */
	private View rightLayout;

	/**
	 * 在滑动过程中展示的3D视图
	 */
	private Image3dView image3dView;

	/**
	 * 用于监听侧滑事件的View。
	 */
	private View mBindView;

	/**
	 * 左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
	 */
	private MarginLayoutParams leftLayoutParams;

	/**
	 * 右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
	 */
	private MarginLayoutParams rightLayoutParams;

	/**
	 * 3D视图的参数,通过此参数来重新确定3D视图的宽度。
	 */
	private ViewGroup.LayoutParams image3dViewParams;

	/**
	 * 用于计算手指滑动的速度。
	 */
	private VelocityTracker mVelocityTracker;

	/**
	 * 重写SlidingLayout的构造函数,其中获取了屏幕的宽度。
	 * 
	 * @param context
	 * @param attrs
	 */
	public ThreeDSlidingLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		screenWidth = wm.getDefaultDisplay().getWidth();
		touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
	}

	/**
	 * 绑定监听侧滑事件的View,即在绑定的View进行滑动才可以显示和隐藏左侧布局。
	 * 
	 * @param bindView
	 *            需要绑定的View对象。
	 */
	public void setScrollEvent(View bindView) {
		mBindView = bindView;
		mBindView.setOnTouchListener(this);
	}

	/**
	 * 将屏幕滚动到左侧布局界面,滚动速度设定为10.
	 */
	public void scrollToLeftLayout() {
		image3dView.clearSourceBitmap();
		new ScrollTask().execute(-10);
	}

	/**
	 * 将屏幕滚动到右侧布局界面,滚动速度设定为-10.
	 */
	public void scrollToRightLayout() {
		image3dView.clearSourceBitmap();
		new ScrollTask().execute(10);
	}

	/**
	 * 左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。
	 * 
	 * @return 左侧布局完全显示返回true,完全隐藏返回false。
	 */
	public boolean isLeftLayoutVisible() {
		return isLeftLayoutVisible;
	}

	/**
	 * 在onLayout中重新设定左侧布局和右侧布局的参数。
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (changed && !loadOnce) {
			// 获取左侧布局对象
			leftLayout = findViewById(R.id.menu);
			leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
			rightEdge = -leftLayoutParams.width;
			// 获取右侧布局对象
			rightLayout = findViewById(R.id.content);
			rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
			rightLayoutParams.width = screenWidth;
			rightLayout.setLayoutParams(rightLayoutParams);
			// 获取3D视图对象
			image3dView = (Image3dView) findViewById(R.id.image_3d_view);
			// 将左侧布局传入3D视图中作为生成源
			image3dView.setSourceView(leftLayout);
			loadOnce = true;
		}
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		createVelocityTracker(event);
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 手指按下时,记录按下时的横坐标
			xDown = event.getRawX();
			yDown = event.getRawY();
			slideState = DO_NOTHING;
			break;
		case MotionEvent.ACTION_MOVE:
			// 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局
			xMove = event.getRawX();
			yMove = event.getRawY();
			int moveDistanceX = (int) (xMove - xDown);
			int moveDistanceY = (int) (yMove - yDown);
			checkSlideState(moveDistanceX, moveDistanceY);
			switch (slideState) {
			case SHOW_MENU:
				rightLayoutParams.rightMargin = -moveDistanceX;
				onSlide();
				break;
			case HIDE_MENU:
				rightLayoutParams.rightMargin = rightEdge - moveDistanceX;
				onSlide();
				break;
			default:
				break;
			}
			break;
		case MotionEvent.ACTION_UP:
			xUp = event.getRawX();
			int upDistanceX = (int) (xUp - xDown);
			if (isSliding) {
				// 手指抬起时,进行判断当前手势的意图
				switch (slideState) {
				case SHOW_MENU:
					if (shouldScrollToLeftLayout()) {
						scrollToLeftLayout();
					} else {
						scrollToRightLayout();
					}
					break;
				case HIDE_MENU:
					if (shouldScrollToRightLayout()) {
						scrollToRightLayout();
					} else {
						scrollToLeftLayout();
					}
					break;
				default:
					break;
				}
			} else if (upDistanceX < touchSlop && isLeftLayoutVisible) {
				scrollToRightLayout();
			}
			recycleVelocityTracker();
			break;
		}
		if (v.isEnabled()) {
			if (isSliding) {
				unFocusBindView();
				return true;
			}
			if (isLeftLayoutVisible) {
				return true;
			}
			return false;
		}
		return true;
	}

	/**
	 * 执行滑动过程中的逻辑操作,如边界检查,改变偏移值,可见性检查等。
	 */
	private void onSlide() {
		checkSlideBorder();
		rightLayout.setLayoutParams(rightLayoutParams);
		image3dView.clearSourceBitmap();
		image3dViewParams = image3dView.getLayoutParams();
		image3dViewParams.width = -rightLayoutParams.rightMargin;
		// 滑动的同时改变3D视图的大小
		image3dView.setLayoutParams(image3dViewParams);
		// 保证在滑动过程中3D视图可见,左侧布局不可见
		showImage3dView();
	}

	/**
	 * 根据手指移动的距离,判断当前用户的滑动意图,然后给slideState赋值成相应的滑动状态值。
	 * 
	 * @param moveDistanceX
	 *            横向移动的距离
	 * @param moveDistanceY
	 *            纵向移动的距离
	 */
	private void checkSlideState(int moveDistanceX, int moveDistanceY) {
		if (isLeftLayoutVisible) {
			if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0) {
				isSliding = true;
				slideState = HIDE_MENU;
			}
		} else if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX > 0
				&& Math.abs(moveDistanceY) < touchSlop) {
			isSliding = true;
			slideState = SHOW_MENU;
		}
	}

	/**
	 * 在滑动过程中检查左侧菜单的边界值,防止绑定布局滑出屏幕。
	 */
	private void checkSlideBorder() {
		if (rightLayoutParams.rightMargin > leftEdge) {
			rightLayoutParams.rightMargin = leftEdge;
		} else if (rightLayoutParams.rightMargin < rightEdge) {
			rightLayoutParams.rightMargin = rightEdge;
		}
	}

	/**
	 * 判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,
	 * 就认为应该滚动将左侧布局展示出来。
	 * 
	 * @return 如果应该滚动将左侧布局展示出来返回true,否则返回false。
	 */
	private boolean shouldScrollToLeftLayout() {
		return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
	}

	/**
	 * 判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,
	 * 或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
	 * 
	 * @return 如果应该滚动将右侧布局展示出来返回true,否则返回false。
	 */
	private boolean shouldScrollToRightLayout() {
		return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
	}

	/**
	 * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
	 * 
	 * @param event
	 *            右侧布局监听控件的滑动事件
	 */
	private void createVelocityTracker(MotionEvent event) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);
	}

	/**
	 * 获取手指在右侧布局的监听View上的滑动速度。
	 * 
	 * @return 滑动速度,以每秒钟移动了多少像素值为单位。
	 */
	private int getScrollVelocity() {
		mVelocityTracker.computeCurrentVelocity(1000);
		int velocity = (int) mVelocityTracker.getXVelocity();
		return Math.abs(velocity);
	}

	/**
	 * 回收VelocityTracker对象。
	 */
	private void recycleVelocityTracker() {
		mVelocityTracker.recycle();
		mVelocityTracker = null;
	}

	/**
	 * 使用可以获得焦点的控件在滑动的时候失去焦点。
	 */
	private void unFocusBindView() {
		if (mBindView != null) {
			mBindView.setPressed(false);
			mBindView.setFocusable(false);
			mBindView.setFocusableInTouchMode(false);
		}
	}

	/**
	 * 保证此时让左侧布局不可见,3D视图可见,从而让滑动过程中产生3D的效果。
	 */
	private void showImage3dView() {
		if (image3dView.getVisibility() != View.VISIBLE) {
			image3dView.setVisibility(View.VISIBLE);
		}
		if (leftLayout.getVisibility() != View.INVISIBLE) {
			leftLayout.setVisibility(View.INVISIBLE);
		}
	}

	class ScrollTask extends AsyncTask<Integer, Integer, Integer> {

		@Override
		protected Integer doInBackground(Integer... speed) {
			int rightMargin = rightLayoutParams.rightMargin;
			// 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
			while (true) {
				rightMargin = rightMargin + speed[0];
				if (rightMargin < rightEdge) {
					rightMargin = rightEdge;
					break;
				}
				if (rightMargin > leftEdge) {
					rightMargin = leftEdge;
					break;
				}
				publishProgress(rightMargin);
				// 为了要有滚动效果产生,每次循环使线程睡眠5毫秒,这样肉眼才能够看到滚动动画。
				sleep(5);
			}
			if (speed[0] > 0) {
				isLeftLayoutVisible = false;
			} else {
				isLeftLayoutVisible = true;
			}
			isSliding = false;
			return rightMargin;
		}

		@Override
		protected void onProgressUpdate(Integer... rightMargin) {
			rightLayoutParams.rightMargin = rightMargin[0];
			rightLayout.setLayoutParams(rightLayoutParams);
			image3dViewParams = image3dView.getLayoutParams();
			image3dViewParams.width = -rightLayoutParams.rightMargin;
			image3dView.setLayoutParams(image3dViewParams);
			showImage3dView();
			unFocusBindView();
		}

		@Override
		protected void onPostExecute(Integer rightMargin) {
			rightLayoutParams.rightMargin = rightMargin;
			rightLayout.setLayoutParams(rightLayoutParams);
			image3dViewParams = image3dView.getLayoutParams();
			image3dViewParams.width = -rightLayoutParams.rightMargin;
			image3dView.setLayoutParams(image3dViewParams);
			if (isLeftLayoutVisible) {
				// 保证在滑动结束后左侧布局可见,3D视图不可见。
				image3dView.setVisibility(View.INVISIBLE);
				leftLayout.setVisibility(View.VISIBLE);
			}
		}
	}

	/**
	 * 使当前线程睡眠指定的毫秒数。
	 * 
	 * @param millis
	 *            指定当前线程睡眠多久,以毫秒为单位
	 */
	private void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
代码比较长,我还是带着大家来理一下思路。首先在onLayout方法中,我们分别初始化了左侧布局对象、右侧布局对象和Image3dView对象,这三个对象稍后都要配置到Activity布局里面的。在onLayout()方法的最后,调用了Image3dView的setSourceView()方法,并将左侧布局对象传了进去,说明我们后面就要对它进行镜像复制。

当手指在界面上拖动来显示左侧布局的时候,就会进入到onTouch()方法中,这里会调用checkSlideState()方法来检查滑动的状态,以判断用户是想要显示左侧布局还是隐藏左侧布局,然后根据手指滑动的距离对右侧布局进行偏移,就可以实现基本的滑动效果了。接下来是重点内容,这里会根据右侧布局的偏移量来改变Image3dView的宽度,当Image3dView大小发生改变时,当然会调用onDraw()方法来进行重绘,此时我们编写的三维旋转逻辑就可以得到执行了,于是就会产生立体的推拉门式效果。注意,在整个的滑动过程中,真正的左侧布局一直都是不可见的,我们所看到的只是它的一张镜像图片。

当手指离开屏幕后,会根据当前的移动距离来决定是显示左侧布局还是隐藏左侧布局,并会调用scrollToLeftLayout()方法或scrollToRightLayout()方法来完成后续的滚动操作。当整个滚动操作完成之后,才会将真正的左侧布局显示出来,再把镜像图片隐藏掉,这样用户就可以点击左侧布局上按钮之类的东西了。

接着我们需要在Activity的布局文件当中去引用这个三维滑动菜单框架,打开或新建activity_main.xml作为程序的主布局文件,代码如下所示:

  1. <com.example.slidinglayout3d.ThreeDSlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/slidingLayout"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent" >  
  6.   
  7.     <RelativeLayout  
  8.         android:id="@+id/menu"  
  9.         android:layout_width="270dip"  
  10.         android:layout_height="fill_parent"  
  11.         android:layout_alignParentLeft="true"  
  12.         android:background="#00ccff"  
  13.         android:visibility="invisible" >  
  14.   
  15.         <LinearLayout  
  16.             android:layout_width="match_parent"  
  17.             android:layout_height="wrap_content"  
  18.             android:layout_centerInParent="true"  
  19.             android:orientation="vertical" >  
  20.   
  21.             <TextView  
  22.                 android:layout_width="wrap_content"  
  23.                 android:layout_height="wrap_content"  
  24.                 android:layout_gravity="center_horizontal"  
  25.                 android:text="This is menu"  
  26.                 android:textColor="#000000"  
  27.                 android:textSize="28sp" />  
  28.   
  29.             <Button  
  30.                 android:layout_width="wrap_content"  
  31.                 android:layout_height="wrap_content"  
  32.                 android:layout_gravity="center_horizontal"  
  33.                 android:text="Test Button" />  
  34.         </LinearLayout>  
  35.     </RelativeLayout>  
  36.   
  37.     <LinearLayout  
  38.         android:id="@+id/content"  
  39.         android:layout_width="320dip"  
  40.         android:layout_height="fill_parent"  
  41.         android:layout_alignParentRight="true"  
  42.         android:background="#e9e9e9"  
  43.         android:orientation="vertical"  
  44.         android:visibility="visible" >  
  45.   
  46.         <Button  
  47.             android:id="@+id/menuButton"  
  48.             android:layout_width="wrap_content"  
  49.             android:layout_height="wrap_content"  
  50.             android:text="Menu" />  
  51.   
  52.         <ListView  
  53.             android:id="@+id/contentList"  
  54.             android:layout_width="fill_parent"  
  55.             android:layout_height="fill_parent"  
  56.             android:cacheColorHint="#00000000" >  
  57.         </ListView>  
  58.     </LinearLayout>  
  59.   
  60.     <com.example.slidinglayout3d.Image3dView  
  61.         android:id="@+id/image_3d_view"  
  62.         android:layout_width="wrap_content"  
  63.         android:layout_height="wrap_content"  
  64.         android:layout_alignParentLeft="true"  
  65.         android:visibility="invisible" />  
  66.   
  67. </com.example.slidinglayout3d.ThreeDSlidingLayout>  
<com.example.slidinglayout3d.ThreeDSlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/slidingLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:id="@+id/menu"
        android:layout_width="270dip"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:background="#00ccff"
        android:visibility="invisible" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:orientation="vertical" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:text="This is menu"
                android:textColor="#000000"
                android:textSize="28sp" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:text="Test Button" />
        </LinearLayout>
    </RelativeLayout>

    <LinearLayout
        android:id="@+id/content"
        android:layout_width="320dip"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:background="#e9e9e9"
        android:orientation="vertical"
        android:visibility="visible" >

        <Button
            android:id="@+id/menuButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Menu" />

        <ListView
            android:id="@+id/contentList"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:cacheColorHint="#00000000" >
        </ListView>
    </LinearLayout>

    <com.example.slidinglayout3d.Image3dView
        android:id="@+id/image_3d_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:visibility="invisible" />

</com.example.slidinglayout3d.ThreeDSlidingLayout>
可以看到,在最外层的ThreeDSlidingLayout布局里面,我们放入了三个直接子布局,第一个RelativeLayout也就是左侧布局了,里面简单地放了一个TextView和一个按钮。第二个LinearLayout是右侧布局,里面放入了一个按钮和一个ListView,都是用于显示左侧布局而准备的。第三个是Image3dView,当然是用于在滑动过程中显示左侧布局的镜像图片了。

最后,打开或新建MainActivity作为程序的主Activity,在里面加入如下代码:

  1. public class MainActivity extends Activity {  
  2.   
  3.     /** 
  4.      * 侧滑布局对象,用于通过手指滑动将左侧的菜单布局进行显示或隐藏。 
  5.      */  
  6.     private ThreeDSlidingLayout slidingLayout;  
  7.   
  8.     /** 
  9.      * menu按钮,点击按钮展示左侧布局,再点击一次隐藏左侧布局。 
  10.      */  
  11.     private Button menuButton;  
  12.   
  13.     /** 
  14.      * 放在content布局中的ListView。 
  15.      */  
  16.     private ListView contentListView;  
  17.   
  18.     /** 
  19.      * 作用于contentListView的适配器。 
  20.      */  
  21.     private ArrayAdapter<String> contentListAdapter;  
  22.   
  23.     /** 
  24.      * 用于填充contentListAdapter的数据源。 
  25.      */  
  26.     private String[] contentItems = { "Content Item 1""Content Item 2""Content Item 3",  
  27.             "Content Item 4""Content Item 5""Content Item 6""Content Item 7",  
  28.             "Content Item 8""Content Item 9""Content Item 10""Content Item 11",  
  29.             "Content Item 12""Content Item 13""Content Item 14""Content Item 15",  
  30.             "Content Item 16" };  
  31.   
  32.     @Override  
  33.     protected void onCreate(Bundle savedInstanceState) {  
  34.         super.onCreate(savedInstanceState);  
  35.         setContentView(R.layout.activity_main);  
  36.         slidingLayout = (ThreeDSlidingLayout) findViewById(R.id.slidingLayout);  
  37.         menuButton = (Button) findViewById(R.id.menuButton);  
  38.         contentListView = (ListView) findViewById(R.id.contentList);  
  39.         contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,  
  40.                 contentItems);  
  41.         contentListView.setAdapter(contentListAdapter);  
  42.         // 将监听滑动事件绑定在contentListView上  
  43.         slidingLayout.setScrollEvent(contentListView);  
  44.         menuButton.setOnClickListener(new OnClickListener() {  
  45.             @Override  
  46.             public void onClick(View v) {  
  47.                 if (slidingLayout.isLeftLayoutVisible()) {  
  48.                     slidingLayout.scrollToRightLayout();  
  49.                 } else {  
  50.                     slidingLayout.scrollToLeftLayout();  
  51.                 }  
  52.             }  
  53.         });  
  54.         contentListView.setOnItemClickListener(new OnItemClickListener() {  
  55.             @Override  
  56.             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
  57.                 String text = contentItems[position];  
  58.                 Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();  
  59.             }  
  60.         });  
  61.     }  
  62. }  
public class MainActivity extends Activity {

	/**
	 * 侧滑布局对象,用于通过手指滑动将左侧的菜单布局进行显示或隐藏。
	 */
	private ThreeDSlidingLayout slidingLayout;

	/**
	 * menu按钮,点击按钮展示左侧布局,再点击一次隐藏左侧布局。
	 */
	private Button menuButton;

	/**
	 * 放在content布局中的ListView。
	 */
	private ListView contentListView;

	/**
	 * 作用于contentListView的适配器。
	 */
	private ArrayAdapter<String> contentListAdapter;

	/**
	 * 用于填充contentListAdapter的数据源。
	 */
	private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
			"Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
			"Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
			"Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
			"Content Item 16" };

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		slidingLayout = (ThreeDSlidingLayout) findViewById(R.id.slidingLayout);
		menuButton = (Button) findViewById(R.id.menuButton);
		contentListView = (ListView) findViewById(R.id.contentList);
		contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
				contentItems);
		contentListView.setAdapter(contentListAdapter);
		// 将监听滑动事件绑定在contentListView上
		slidingLayout.setScrollEvent(contentListView);
		menuButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				if (slidingLayout.isLeftLayoutVisible()) {
					slidingLayout.scrollToRightLayout();
				} else {
					slidingLayout.scrollToLeftLayout();
				}
			}
		});
		contentListView.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				String text = contentItems[position];
				Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
			}
		});
	}
}
这些代码应该都非常简单和眼熟了吧,和以前滑动菜单中的代码完全一样,调用ThreeDSlidingLayout的setScrollEvent方法,将ListView作为绑定布局传入,这样就可以通过拖动ListView来显示或隐藏左侧布局。并且在按钮的点击事件里也加入了显示和隐藏左侧布局的逻辑。

好了,这样所有的编码工作就已经完成了,让我们来运行一下吧,效果如下图所示:

                                                 

怎么样?效果非常炫丽吧!其实只要对Camera进行巧妙地运用,还可以编写出很多非常精彩的特效,就看你敢不敢去发挥你的想象力了。

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

如果喜欢我写的文章,请小手一抖,为我投上一票,真心非常感谢你的支持。

投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/sinyu890807

源码下载,请点击这里

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 3D滑动菜单完全解析,实现推拉门式的立体特效 的相关文章

  • android.app包---------ActivityManager类介绍

    ActivityManager类介绍 ActivityManager与系统与所有正在运行Activity进行交互 相关方法介绍 getDeviceConfigurationInfo 获取设备配置属性值 getMemoryClass 返回当前
  • 使用fastboot工具刷入recovery.img、boot.img、system.img等

    下载解压 fastboot工具 解压FastBoot工具 zip 将解压得到的 FastBoot文件夹复制到任意盘如 D盘 将要刷入手机的recovery img recovery img等放到FastBoot文件夹 安装好手机型号对应的U
  • Android 3D滑动菜单完全解析,实现推拉门式的立体特效

    转载请注明出处 http blog csdn net guolin blog article details 10471245 在上一篇文章中 我们学习了Camera的基本用法 并借助它们编写了一个例子 实现了类似于API Demos里的图
  • UncaughtExceptionHandler示例使用

    概述 UncaughtExceptionHandler是用来catch线程内的没有被捕获到的exception 可以在uncaughtException方法中对这些异常进行统一处理 用法 UncaughtExceptionHandler是一
  • Android RecycleView列表使用GridLayoutManager 均分子项Item,且左右宽度相同

    1 需求描述 列表分为一列两个 两个子项宽度一致 且左右边距一样 就是要好看 子项宽度适配手机 高度适配宽度 2 问题描述 但是我们直接设置的时候 他的子项会在给他的布局的左边 于是我想到了设置下子项的左右边距 但是android手机屏幕太
  • android.accounts包

    包 android accounts 英文原文 http developer android com reference android accounts package summary html 版本 Android 4 0 r1 译者署
  • Android数据库安全解决方案,使用SQLCipher进行加解密

    转载请注明出处 http blog csdn net guolin blog article details 11952409 我们都知道 Android系统内置了SQLite数据库 并且提供了一整套的API用于对数据库进行增删改查操作 数
  • Java中的四种引用

    Java中存在四种引用 它们分别是 1 强引用 StrongReference 强引用是使用最普遍的引用 如果一个对象具有强引用 那垃圾回收器绝不会回收它 当内存空间不足 Java虚拟机宁愿抛出OutOfMemoryError错误 使程序异
  • Android res文件夹下资源定义及使用

    1 颜色 RGB ARGB RRGGBB AARRGGBB 颜色资源应该位于
  • Java中String类的isEmpty方法、null以及""的区别

    一直以来对String的这三个空挺晕的 刚好同事问我 我也学习下 从别人博客上看到的是这样的 isEmpty 分配了内存空间 值为空 是绝对的空 是一种有值 值 空 分配了内存空间 值为空字符串 是相对的空 是一种有值 值 空字串 null
  • android 日期控件

    相关布局文件
  • Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能

    转载请注明出处 http blog csdn net guolin blog article details 9255575 最近项目中需要用到ListView下拉刷新的功能 一开始想图省事 在网上直接找一个现成的 可是尝试了网上多个版本的
  • android.content包-----ClipboardManager

    ClipboardManager类介绍 Clipboardmanager类通过getSystemService String 方法进行实例化操作 ClipboardManger类的相关方法很简单 包含set和get剪切板的数据 剪切板的数据
  • WebView 加载不出网页,一片空白

    今天在项目上加载网页时 发现一只加载不出来 emmm 就看了下以往的项目 发现遗漏的地方不止一点哦 在此做个总结 1 权限配置 确保在 AndroidManifest xml 文件中添加了网络权限
  • android获取string.xml的值

    为什么需要把应用中出现的文字单独存放在string xml文件中呢 一 是为了国际化 当需要国际化时 只需要再提供一个string xml文件 把里面的汉子信息都修改为对应的语言 如 English 再运行程序时 android操作系统会根
  • Android Studio如何添加工程(project)为library(针对非gradle)

    这篇文章还是针对非gradle build的工程 gradle build有一些差别 在Eclipse要引用别的工程为本工程的library很简单 但是在Android Studio还是稍稍有点小复杂的 那如何引用别的工程为本工程的libr
  • Android SurfaceView

    下面就贴上一个小程序代码 主要运用SurfaceView来实现在屏幕上画一个圆 你可以通过按方向键和触摸屏幕来改变圆的位置 代码 Activity java view plain copy print package com view im
  • android.accessibilityservice包介绍

    android accessibilityservice 英文原文 http developer android com reference android accessibilityservice package summary html
  • 转载的开源干货

    android相关 第三方库 awesome android ui 大量 Android UI UX 库 大城小黄 recyclerview animators 一个关于RecyclerView items的动画库 一个简单的Recycle
  • ANDROID版本号和版本名称的重要性介绍

    转载请注明出处http blog csdn net y150481863 article details 41249159 来自 http blog csdn net y150481863 当我们在刚开始学习ANDROID的时候 可能不会过

随机推荐

  • uiautomator2滑动操作d.swipe_ext(),及判断是否ui到顶部

    d swipe ext up 向上滑动 d swipe ext down 向下滑动 d swipe ext left 向左滑动 d swipe ext right 向右滑动 判断当前页面是否在底部 while True d swipe ex
  • java 反射和注解

    文章目录 1 反射 1 1定义反射 1 2java代码的三个阶段 1 3将类的各个部分封装的对象 1 4获取 1 4 1获取类名 1 4 2获取成员变量 1 4 3获取构造方法 1 4 4获取成员方法 1 4 5获取注解 2 注解 2 1概
  • 调试之设置数据断点 (zz.IS2120)

    How to Set a Data Breakpoint Native Only z 2012 09 11 09 43 48 IS2120 CSDN T3678804781 T31 L404 R7 V204 Data breakpoints
  • 【计算机网络】Linux环境中的网络套接字编程

    文章目录 前言 一 预备知识 理解源IP地址和目的IP地址 认识端口号 认识UDP协议和TCP协议 了解网络字节序 二 socket 套接字 socket 常见API sockaddr 和 sockaddr in 三 UDP Socket编
  • bash脚本-centos7安装docker

    bin bash set ex sudo yum remove docker docker client docker client latest docker common docker latest docker latest logr
  • 蓝桥杯:试题 算法训练 星际交流 康托展开

    题目 资源限制 时间限制 1 0s 内存限制 256 0MB 问题描述 人类终于登上了火星的土地并且见到了神秘的火星人 人类和火星人都无法理解对方的语言 但是我们的科学家发明了一种用数字交流的方法 这种交流方法是这样 的 首先 火星人把一个
  • MATLAB计算矩阵的逆和广义逆

    当矩阵的行数等于列数时 计算矩阵的逆 可直接使用 inv A 当矩阵的行数不等于列数时 可以考虑计算矩阵的Moore Penrose逆 有两种方法 第一 直接使用Moore Penrose逆的而定义B inv A A A 第二 使用命令B
  • RedisTemplate redis缓存的基本使用

    文章目录 RedisTemplate redis缓存的基本使用 放入缓存 并缓存设置过期时间 时间单位为秒如果值小于或等于0为过期时间无限期 放入缓存 过期时间无限制 如果key存在不改变其值 不存在此key就放入缓存 并且key存在返回f
  • error: Your local changes to the following files would be overwritten by merge: .DS_Store

    Git ignore gitignore 有时候会遇到如下提示 error Your local changes to the following files would be overwritten by merge DS Store b
  • 高德API+Echarts 实现3D地图展示图表

    效果图 前期准备 所需依赖 echarts amap amap jsapi loader npm i echarts amap amap jsapi loader 代码实现 3D地图 div div 您在2021年12月02日以后申请的ke
  • 【OpenWRT之旅】如何自定义一个配置文件的设置界面

    1 引言 OpenWRT中采用LuCI作为它的Web interface界面框架 采用Lua语言 在本文中将以一个简单的示例详细描述如何自定义开发一个界面 对一个配置文件进行操作 2 Model与Controler MVC的设计理念是进行L
  • JS中的键盘事件(onkeydown、onkeyup、keyCode)

    键盘事件 okeydown 键盘被按下 如果一直按着键盘的按键 则okeydown事件会一直被触发 当键盘按键一直被按住的时候 事件被连续触发 第一次和第二次以及后面的n次之间 触发的时间间隔会稍长 在0 5秒左右 一直按着按键 事件连续触
  • 麒麟系统里如何通过命令查询当前系统的具体信息

    命令 cat etc kyinfo 获取系统详细信息 包含架构 包含当前系统更新的时间等 cat etc productinfo 部分环境可能不能通过此命令获取信息 R系 nkvers 其他查询命令 操作系统版本查询命令 cat etc k
  • 前端性能优化认知

    前端性能优化认知 什么是前端性能优化 通常来讲 前端性能优化是指 从用户开始访问网站到整个页面完整地展现出来的过程中 通过各种优化策略和优化方法 让页面加载得更快 让用户的操作相应更及时 给用户更好的使用体验 优化是在做什么 如上图所示 优
  • ESP8266-01S烧录MQTT透传AT固件

    一 ESP8266 01S模块硬件连接 须在正常模式下 VCC GND RX TX 将IO0引脚接地 EN引脚接3 3V 总共6根线 连入USB TO TTL模块 将USB TO TTL模块插入电脑 二 固件与工具均可在安信可官网下载 也可
  • Element ui 中将switch开关自定义文字描述(ON/OFF)显示在开关里面

    Element ui 中将switch开关自定义文字描述 ON OFF 显示在开关里面 官网示例 可以看出文字描述在开关两边 看着很别扭 上代码
  • java中synchronized的三种写法详解

    预备知识 首先 我们得知道在java中存在三种变量 实例变量 存在于堆中 静态变量 存在于方法区中 局部变量 存在于栈中 然后 我们得明白 合适会发生高并发不安全 条件1 多线程并发 条件2 有共享数据 条件3 共享数据有修改的行为 具体不
  • python装饰器全解--包含实现原理及应用场景

    装饰器是Python的一大重点和难点 也是后续学习对类进行装饰以及元类的基础 其允许Python实现装饰器设计模式 可以在不改变某函数结构和调用方式基础之上 为其增加新的功能 并且最大化复用新的功能 装饰器在面向切面编程的场景中很有用 比如
  • MySQL复习总结

    1 char 和 varchar的区别 char的长度是不可变的 排除掉所存储的字符 其余位置使用空格进行填充 检索char类型的数据时会对尾部的空格进行删除 一个英文字符占用一个字节 一个汉字占用两个字节 varchar的长度是可变的 v
  • Android 3D滑动菜单完全解析,实现推拉门式的立体特效

    转载请注明出处 http blog csdn net guolin blog article details 10471245 在上一篇文章中 我们学习了Camera的基本用法 并借助它们编写了一个例子 实现了类似于API Demos里的图