最近又浏览了一遍github上的开源项目,有感于大神们的强大,所以想搞搞像kugou那样左右滑动的侧滑菜单控件。
打开移动设备的kugou软件,看了下整体效果,就知道了,我们要实现一个左侧主页,右侧菜单,拉动时以缩放式动画进场的控件。
创建项目等一些简单步骤就不说了。让我们来看下主要实现:
自定义一个控件DragLayout,继承自FrameLayout。
public class DragLayout extends FrameLayout {
private boolean isShowShadow = false;
/** * GestureDetectorCompat处理手势识别 * OnGestureListener有下面的几个动作: * 按下(onDown):刚刚手指接触到触摸屏的那一刹那,就是触的那一下。 * 抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。 * 长按(onLongPress): 手指按在持续一段时间,并且没有松开。 * 滚动(onScroll): 手指在触摸屏上滑动。 * 按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。 * 抬起(onSingleTapUp):手指离开触摸屏的那一刹那。 */
private GestureDetectorCompat gestureDetector;
private ViewDragHelper dragHelper; // 简化view拖拽操作的帮助类
private DragListener dragListener;
private int range; //菜单关闭时跟原点的距离
private int width;
private int height;
private int mainLeft; //侧菜单距左侧原点位置的距离
private Context context;
// private ImageView iv_shadow;
private RelativeLayout vg_right;
private MyRelativeLayout vg_main;
private Status status = Status.Close;//默认菜单关闭
private boolean isFirstInflate = true;//用来帮助mainLeft第一次获取距离时的
//布尔型,初始化为真,在onSizeChanged()中改变
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
this.context = context;
}
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//初始化手势识别类与view拖曳类
gestureDetector = new GestureDetectorCompat(context,
new YScrollDetector());
dragHelper = ViewDragHelper.create(this, dragHelperCallback);
}
}
首先创建构造器,并初始化GestureDetectorCompat类与ViewDragHelper类。
GestureDetectorCompat类是手势识别类,它对外提供了两个接口:OnGestureListener,OnDoubleTapListener,还有一个内部类SimpleOnGestureListener;SimpleOnGestureListener类是GestureDetector提供给我们的一个更方便的响应不同手势的类,它实现了上述两个接口。
ViewDragHelper类是android简化view拖拽操作的帮助类,作用在一个ViewGroup上,也就是说他不能直接作用到被拖拽的view, 其实这也很好理解,因为view在布局中的位置是父ViewGroup决定的。这个类初始化时,需将ViewDragHelper.Callback参数传进。这个回调也是控件实现侧滑的主要代码,它是连接ViewDragHelper与View之间的桥梁。
private ViewDragHelper.Callback dragHelperCallback = new ViewDragHelper.Callback() {
/** * 处理x方向拖动的,返回值该child现在的位置 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (mainLeft + dx < 0) { //右侧菜单距离原点的位置最小为0,最大为range
return 0;
} else if (mainLeft + dx > range) {
return range;
} else {
return left;
}
}
/** * tryCaptureView(View view, int pointerId) 表示尝试捕获子view,这里一定要返回true, * 返回true表示允许。 */
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
/** * 获取边界 */
@Override
public int getViewHorizontalDragRange(View child) {
return width;
}
/** * 该方法在手势拖动释放的时候被调用,可以在这里设置子View预期到达的位置, * 如果人为的手势拖动没有到达预期位置,我们可以让子View在人为的拖动结束后,再自动关的滑动到指定位置 */
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// if (xvel > 0) {
// open();
// } else if (xvel < 0) {
// close();
// } else
if (releasedChild == vg_main && mainLeft < range * 0.3) {
//当拉动主页时,如果mainLeft小于 0.3range,则自动打开
open();
} else if (releasedChild == vg_right && mainLeft < range * 0.7) {
//当拉动菜单页时,如果mainLeft大于 0.7range,则自动打开
open();
} else {//其余都为关闭情况
close();
}
}
private final static String tag = "Draglayout";
/** * 该方法在子view位置发生改变时都会被调用,可以在这个方法中做一些拖动过程中渐变的动画等操作 */
@Override
public void onViewPositionChanged(View changedView, int left, int top,int dx, int dy) {
//根据不同的子view,对mainLeft进行分别赋值
if (changedView == vg_right) { //右侧菜单
mainLeft = left;
Log.d(tag, "vg_main: " + mainLeft + " range: " + range);
} else { //主页
Log.d(tag, "vg_left1: " + mainLeft);
mainLeft = mainLeft + left;
Log.d(tag, "vg_left2: " + mainLeft + " left: " + left);
}
//mainLeft的取值范围
if (mainLeft < 0) {
mainLeft = 0;
} else if (mainLeft > range) {
mainLeft = range;
}
//
// if (isShowShadow) {
// iv_shadow.layout(mainLeft, 0, mainLeft + width, height);
// }
if (changedView == vg_main) {
vg_main.layout(0, 0, width, height);
vg_right.layout(mainLeft, 0, mainLeft + width, height);
}
dispatchDragEvent(mainLeft);
}
};
它主要是实现了以上几种方法,具体方法做何用,可看注释。
在这个方法里,我们需要判断操作的对象,是主界面还是菜单,根据对象的不同,给予mainLeft(侧菜单距左侧原点位置的距离)不同的赋值。在onViewReleased()方法里,判断mainLeft的大小,去打开或者关闭菜单。在onViewPositionChanged()中,对两个界面作拖动过程中的渐变动画。
因为将由ViewDragHelper处理view拖动事件,所以DragLayout的触摸事件也需一并给它处理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev)
&& gestureDetector.onTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
try {
//必须将layout的触摸事件交由DragHelper类处理!
dragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
事件都定义好了,我们还需要实现两个方法:onSizeChanged()和onLayout()
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = vg_main.getMeasuredWidth();
height = vg_main.getMeasuredHeight();
range = (int) (width * 0.8f);
//第一次界面初始化时,根据获得的range值对mainLeft进行赋值
if (isFirstInflate) {
mainLeft = range;
isFirstInflate = false;
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
vg_main.layout(0, 0, width, height);
vg_right.layout(mainLeft, 0, mainLeft + width, height);
}
在onSizeChanged()获取屏幕的高宽,并得到range值(菜单关闭时跟原点的距离)与初始化mainLeft.
其实,主要的实现代码也就是上面这些了,其他的比如渐变动画,以及点击按钮后的拉取与关闭菜单都比较简单,下面有代码下载链接,可以下载下来看看,这是从一个开源项目拷下来做了修改的,变成自己喜欢的风格。所以大神们勿喷哦!
效果图就不上了,不过保证可以使用哦。
最后发现gif好难弄,有大神会的,麻烦给我推荐个工具,可以生成移动设备上的简易gif动画。不胜感激!