本人也不是大牛,有讲解的不对的地方,还请指出。
效果如上图,在讲解之前,需要感谢specialcyci提供的此控件,对本控件有更好的想法或建议,可以联系[email protected],下面开始讲解实现过程。
本效果主要应用到知识点:fragment,AnimatorSet,ScrollView,GestureDetector
(由于篇幅有限,我就只介绍核心代码了,需要完整代码可以自己clone git@oschina 的项目,路径:http://git.oschina.net/kymjs/KJmusic)
首先:介绍一下本界面的结构,在左侧的MenuList,是一个由ScrollView包裹的LinearLayout;在右侧看到的缩略图效果的content界面,是一个由fragment构成的Activity(立体效果是由一张shadow图片和模糊背景图片产生的)
接着来讲实现方式:
左侧的MenuList,每一个menu item可以通过继承LinearLayout的形式在代码中完成,但是本人更推荐在layout中用xml布局中完成(只有一个imageView一个TextView,很简单)
整个菜单界面(图中的绿色部分)以继承LinearLayout的形式实现,这也是原作的实现方式,当然我更推荐使用xml文件的形式,以下是我对整个menu界面的布局文件(很简单,一个FrameLayout中放一个全屏的imageView做背景,一个ScrollView包裹的LinearLayout做菜单列表,以及一个imageView做阴影部分)
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/iv_background" /> <ImageView android:id="@+id/iv_shadow" /> <ScrollView android:id="@+id/sv_menu" > <LinearLayout android:id="@+id/layout_menu" > </LinearLayout> </ScrollView> </FrameLayout>
紧接着是本效果的两个重点动画和手势:
我们需要设置两个动画分别用来作为content的显示和收缩
可以通过调用AnimatorSet类对象的playTogether()方法设置
/** * 创建菜单退出的动画效果 * * @param target * @param targetScaleX * @param targetScaleY * 这里的ViewHelper类是来自nineoldandroids 的jar包 * 第一个参数是哪个View需要设置本动画(当然是Activity了),第二、三个参数是动画的坐标 * @return */ private AnimatorSet buildScaleDownAnimation(View target, float targetScaleX, float targetScaleY) { int pivotX = (int) (getScreenWidth() * 1.5); int pivotY = (int) (getScreenHeight() * 0.5); ViewHelper.setPivotX(target, pivotX); ViewHelper.setPivotY(target, pivotY); AnimatorSet scaleDown = new AnimatorSet(); scaleDown.playTogether( ObjectAnimator.ofFloat(target, "scaleX", targetScaleX), ObjectAnimator.ofFloat(target, "scaleY", targetScaleY)); scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity, android.R.anim.decelerate_interpolator)); scaleDown.setDuration(250); return scaleDown; } /** * 创建菜单打开的动画效果 * * @param target * @param targetScaleX * @param targetScaleY * @return */ private AnimatorSet buildScaleUpAnimation(View target, float targetScaleX, float targetScaleY) { AnimatorSet scaleUp = new AnimatorSet(); scaleUp.playTogether( ObjectAnimator.ofFloat(target, "scaleX", targetScaleX), ObjectAnimator.ofFloat(target, "scaleY", targetScaleY)); scaleUp.setDuration(250); return scaleUp; }
对于触摸事件的使用,核心部分是重写onFling()方法
@Override public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent2, float v, float v2) { if (isInIgnoredView(motionEvent) || isInIgnoredView(motionEvent2)) return false; int distanceX = (int) (motionEvent2.getX() - motionEvent.getX()); int distanceY = (int) (motionEvent2.getY() - motionEvent.getY()); int screenWidth = (int) getScreenWidth(); if (Math.abs(distanceY) > screenWidth * 0.3) return false; if (Math.abs(distanceX) > screenWidth * 0.3) { if (distanceX > 0 && !isOpened) { // from left to right; openMenu(); } else if (distanceX < 0 && isOpened) { // from right th left; closeMenu(); } } return false; }
主要是对一些控件的计算,isinignoredView()方法会在下面讲。
以上就是这个效果的主要内容,也许你看到这里觉得:这没什么特别的啊。没错,的确,如果只是这样的确没什么好说的,而这个效果复杂的地方在于对于触摸屏事件的冲突解决。
在这个问题,原作是通过对整个菜单内的孩子View设置tag的形式作记录,我认为这种做法不是很好,如果当控件很少的时候,是用tag来标记控件的确是个很方便的方法,但是如果控件多了以后,查找起来就很费力,所以本人推荐创建一个集合private List<View> ignoredViews;来存储不需要处理触摸屏事件的View。
通过判断触摸是否发生在需要屏蔽触摸事件的View上,来执行相应的操作
判断的方法(正是上面提到的isinignoredView()方法):
/** * 判断触摸是否发生在 不拦截触摸事件的控件上 * * @Rect类主要用于表示坐标系中的一块矩形区域 * @param ev * @return */ private boolean isInIgnoredView(MotionEvent ev) { Rect rect = new Rect(); for (View v : ignoredViews) { v.getGlobalVisibleRect(rect); // 将view位置保存到rect中 if (rect.contains((int) ev.getX(), (int) ev.getY())) return true; } return false; }
最后,也是一个必要点:要作为一个方便使用的控件,需要对其设置一些回调方法,例如:当菜单打开时,菜单关闭时,菜单关闭后,等方法。(对于回调,这里顺便提一点:回调方法就可以看做是:另一个类中的方法,需要一个接口类型的参数,而这个接口类型的参数会在某种特定情况下,被这个接口的实现类调用)
public interface OnMenuListener { /** * 这个方法调用完成后将调用打开菜单的动画 */ public void openMenu(); /** * 这个方法调用完成后将调用关闭菜单的动画 */ public void closeMenu(); }
至此,在此感谢specialcyci提供的此控件效果,本效果Demo以及项目完整源码在git@china中可以搜索到,这里就不细说了。