概述
最近在用酷狗音乐时发现酷狗音乐的主页侧滑效果不错,忍不住手痒痒,就想实现一下看看
效果分析
- 我们可以看出分为两个部分 (菜单页面、主题页面)所以这里采用自定义ViewGroup
- 向左滑动时会有一个放大和缩小的动画,在这里基本可以确定需要处理触摸事件,及触摸时的动画
- 快速滑动时的打开关闭处理(GestureDetector收拾处理类)
实现思路
- 这里我们选择继承Horizonta梯度怎么求lScrollView 水平滚动布局 来实现,然后写好布局文件这个和HorizontalScrollView的用法动画片汪汪队一样,初时进来滚动到只有主页面的地方,在onT梯度下降法原理ouch中根据滑动的距离来控制布局的缩放动画
实现
public KGSlidingMenu(Context context) {
this(context, null);
}
public KGSlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
运行起来之后发现布局不对,完全乱了,这里我们在代码里设置菜单页面、主题页面的宽度
- 菜单页面的宽度 这里我们定义一个属性 menu_width
- 主题页面的宽度 = 屏幕的宽度
private View mMenuView;
private View mContainerView;
private int mMenuWidth;//需要自定义属性获取
public KGSlidingMenu(Context context) {
this(context, null);
}
public KGSlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.KGSlidingMenu);
mMenuWidth = Math.round(ta.getDimension(R.styleable.KGSlidingMenu_menu_width, 325));
ta.recycle();
}
@Override
protected void onFinishInflate() {//在onCreate中执行
super.onFinishInflate();
//这个方法代表整个布局解析完毕
//指定宽高 :内容页的宽度为屏幕的宽度、菜单页的宽度由使用者自己在xml中定义 app:menu_
//获取菜单和容器View
//获取的是LinearLayout
ViewGroup mRootView = (ViewGroup) getChildAt(0);
if (mRootView == null) {
throw new NullPointerException("child can not be null !!!");
}
if (mRootView.getChildCount() != 2) {
throw new RuntimeException("Only two can be placed View !!!");
}
//获取的是菜单和容器的跟布局
mMenuView = mRootView.getChildAt(0);
mContainerView = mRootView.getChildAt(1);
//给菜单布局设置指定宽度 给容器布局设置屏幕的宽高
//只能通过layoutParams设置宽高
mMenuView.getLayoutParams().width = mMenuWidth;
mContainerView.getLayoutParams().width = getScreenWidth(mContext);
}
//获取屏幕宽度
public int getScreenWidth(Context mContext) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels;
}
目前的效果就是可以滑动,并且菜单和主页面内容的布局宽度正常,现在存在的问题:
- 在刚进来容器技术时,菜单是打开的,正常情况应该是关闭的
- 在手指抬起的时候,应该根据当前位置去判断菜单梯度洗脱时打容器所能容纳什么叫做容器的容积开,还是关闭
- 在滑动时 没有相应的动画
- 第一点 在刚进来时,菜单是打开的,这一点我们可以在布局摆放完成时,是我们KGSlidi动画ngMenu容器苗滚动到相应的位置 具体实现如下
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {//在onResume中执行
super.onLayout(changed, l, t, r, b);
//初始化进来 mMenuView 模式是不显示的
//用来摆放子View,等所有子View 摆放完毕才能滚动
scrollTo(mMenuWidth, 0);
}
-
第二点 在手指抬起的时候,应该根据当前动画片猫和老鼠位application置去判断菜单时打开,还是关闭。判断菜单打开关闭的条件是我们滑动的距离和菜容器苗单的宽做对比
1).在关闭时,向右滑动,如果滑动的距离小于菜单宽度的一半,这是当我们抬起手指时,应当关闭菜单,大于菜单宽度的一apple半时,应当打开菜单.
2).在打开时,向左滑动,如果滑动的距离小于菜单宽度的一appetite半,这是当我们抬起手指时,应当打开菜单,大于菜单appreciate宽度的一半时,应当关闭菜单.
-
因此我们需要在onTouchEvent()中处理相应的触摸滑动事件
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null) {
if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
}
if (onInterceptTouchEvent(event)) return true;
//手指抬起
if (MotionEvent.ACTION_UP == event.getAction()) {
int mCurrentScrollX = getScrollX();
float mCurrentScrollY = getScrollY(); //避免竖向触摸时触发事件
if (Math.abs(mCurrentScrollX) > Math.abs(mCurrentScrollY) * 2 / 3) {
if (mCurrentScrollX < mMenuWidth / 2) {//未滚动到mMenuView宽度的一半
//打开菜单
openMenu();
} else {
//关闭菜单
closeMenu();
}
}
return false;//确保 super.onTouchEvent(event) 不会执行
}
return super.onTouchEvent(event);
}
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0)
}
private void openMenu() {
smoothScrollTo(0, 0);
}
-
第三点 在滑动时 没有相应的动画 通过效果我们可以看到
1). 右边的动画效果为缩放效果
2). 左边的动画效果有渐变、缩放和位移
这些动画执行的进度和效果都是根据滑动的距离来决定时,梯度下降法原理因此我们需要获得滑动的距离
//滚动回调 处理右边View的缩放 左边View的缩放和透明度
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//l:left 变化mMenuWidth -> 0
//算一个梯度值
float scale = 1f * l / mMenuWidth; //从1->0
//计算右边的缩放值
float rightScaleValue = 0.85f + 0.15f * scale;
//设置右边的缩放 默认以view的中心点缩放
ViewCompat.setPivotX(mContainerView, 0);
ViewCompat.setPivotY(mContainerView, mContainerView.getMeasuredHeight() / 2);
ViewCompat.setScaleX(mContainerView, rightScaleValue);
ViewCompat.setScaleY(mContainerView, rightScaleValue);
//设置右边的菜单的透明度 由半透明到全透明 0。85-1
//缩放 0.85-1
//透明度
float leftAlphaValue = (1 - scale) * 0.5f + 0.5f;
ViewCompat.setAlpha(mMenuView, leftAlphaValue);
float leftScaleValue = (1 - scale) * 0.15f + 0.85f;
ViewCompat.setScaleX(mMenuView, leftScaleValue);
ViewCompat.setScaleY(mMenuView, leftScaleValue);
//刚开始退出是在右边而不是左边
//设置平移
// ViewCompat.setTranslationX(mMenuView,l);抽屉效果
ViewCompat.setTranslationX(mMenuView, 0.2f * l);
}
到此我们可以看到效果基本成型了,下面处理一下细节方面的容器是什么优化
- 处理一下在手指快速滑动的容器所能容纳什么叫做容器的容积是效果,如果我们的触摸事件响应了快速滑动的话,就不要执行onTouchEvent中的相关操作,否则会出现效果不对的冲突事件,因此我们需要在onTouchEvent中添加一下这段代码
if (mGestureDetector != null) {
if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
}
mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//快速滑动
//只要快速滑动就会回调
//打开的时候往右快速滑动,就去关闭 关闭的时候往左边快速滑动就去打开
//快速往右边滑动是一个正数 快速往左边滑动是一个负数
if (Math.abs(velocityX) < Math.abs(velocityY) * 2 / 3) return false;
if (isMenuOpen) {
//打开的时候往右快速滑动,就去关闭
if (velocityX < 0) {
closeMenu();
return true;
}
} else {
//关闭的时候往左边快速滑动就去打开
if (velocityX > 0) {
openMenu();
return true;
}
}
return false;
}
});
- 注意查看的话可以发现当菜单打开时,我们们点击菜单的右边主题部分,不会响应主题页面的相关事件,只apple会关闭菜单,只有在菜单关闭时,才会触发右边主题部分的相关事件,因此我们这地方需要在拦截事件onInterceptappearTouchEvent中判断菜单是否关apple闭,进行事件的拦截,因此我们需要在上面打开关闭菜单的方法里添apple加一个标识符isMenuOpen来区分菜单是否打开,这里事件拦截之后我们直接关闭了菜单,所以不需要执行onTouchEvent中梯度的几何意义手指抬起的相关操作。
if (onInterceptTouchEvent(event)) return true;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (isMenuOpen && event.getX() > mMenuView.getWidth() && event.getAction() == MotionEvent.ACTION_UP) {
closeMenu();
return true;
}
return false;
}
至此相关效果基本就完成了,我们来看一下吧