本人也不是大牛,有讲解的不对的地方,还请指出。
效果如上图,在讲解之前,需要感谢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做阴影部分)
紧接着是本效果的两个重点动画和手势:
我们需要设置两个动画分别用来作为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上,来执行相应的操作
判断的方法(正是上面提到的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中可以搜索到,这里就不细说了。