仿QQ6.0主页面侧滑效果

1.概述


最近一直都在带实习生做项目,发现自己好久没有写博客了,这几天更新会比较频繁,今天玩QQ的时候发现QQ主页菜单滑动效果早就变了,实在忍不住晚上就来实现一下了!
  

  

这里写图片描述

2.实现


2.1. 实现的方式多种多样
  2.1.1 自定义ViewGroup ,处理其onTouch事件
  2.1.2 FrameLayout + 手势处理类GestureDetector
  2.2.3 使用Google自带的DrawerLayout 对其进行修改
  2.2.4 继承自水平滚动HorizontalScrollView ***
大家可以看一下这个Android自定义ViewGroup打造各种风格的SlidingMenu,这种方式继承自ViewGroup,个人觉得还行但是还是比较繁琐要处理的东西也比较多,那么我就用最后一种
2.2.4
*的方式实现,有人说直接去网上下载一个源代码就可以了,这我就只能GG了。

2.3. 自定义SlidingMenu extends HorizontalScrollView 然后写好布局文件这个和ScrollView的用法一样,只不过是横向滚动的

1/** 2 * description: 3 * 仿QQ6.0主页面侧滑的自定View 4 * Created by 曾辉 on 2016/11/1. 5 * QQ:240336124 6 * Email: 240336124@qq.com 7 * Version:1.0 8 */ 9public class SlidingMenu extends HorizontalScrollView { 10 public SlidingMenu(Context context) { 11 super(context); 12 } 13 14 public SlidingMenu(Context context, AttributeSet attrs) { 15 super(context, attrs); 16 } 17 18 public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) { 19 super(context, attrs, defStyleAttr); 20 } 21}

2.4. 运行起来之后发现布局不对,完全乱了明明都是match_parent可是就是不行那么我们就需要用代码指定菜单和内容的宽度:
菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度
内容的宽度 = 屏幕的宽度

1/** 2 * description: 3 * 仿QQ6.0主页面侧滑的自定View 4 * Created by 曾辉 on 2016/11/1. 5 * QQ:240336124 6 * Email: 240336124@qq.com 7 * Version:1.0 8 */ 9public class SlidingMenu extends HorizontalScrollView { 10 private View mMenuView; 11 private View mContentView; 12 private int mMenuWidth; 13 14 public SlidingMenu(Context context) { 15 this(context, null); 16 } 17 18 public SlidingMenu(Context context, AttributeSet attrs) { 19 this(context, attrs, 0); 20 } 21 22 public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) { 23 super(context, attrs, defStyleAttr); 24 // 获取自定义的右边留出的宽度 25 TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu); 26 float rightPadding = array.getDimension( 27 R.styleable.SlidingMenu_rightPadding,dip2px(50)); 28 // 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度 29 mMenuWidth = (int) (getScreenWidth() - rightPadding); 30 array.recycle(); 31 } 32 33 /** 34 * 把dip 转成像素 35 */ 36 private float dip2px(int dip) { 37 return TypedValue.applyDimension( 38 TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics()); 39 } 40 41 42 @Override 43 protected void onFinishInflate() { 44 super.onFinishInflate(); 45 46 // 1.获取根View也就是外层的LinearLayout 47 ViewGroup container = (ViewGroup) this.getChildAt(0); 48 49 int containerChildCount = container.getChildCount(); 50 if(containerChildCount>2){ 51 // 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局) 52 throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局"); 53 } 54 55 // 2.获取菜单和内容布局 56 mMenuView = container.getChildAt(0); 57 mContentView = container.getChildAt(1); 58 59 // 3.指定内容和菜单布局的宽度 60 // 3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度 61 mMenuView.getLayoutParams().width = mMenuWidth; 62 // 3.2 内容的宽度 = 屏幕的宽度 63 mContentView.getLayoutParams().width = getScreenWidth(); 64 } 65 66 /** 67 * 获取屏幕的宽度 68 */ 69 public int getScreenWidth() { 70 Resources resources = this.getResources(); 71 DisplayMetrics dm = resources.getDisplayMetrics(); 72 return dm.widthPixels; 73 } 74}

目前的效果就是可以滑动,并且菜单和主页面内容的布局宽度正常

这里写图片描述

2.5 接下来一开始就让菜单滑动到关闭状态,手指滑动抬起判断菜单打开和关闭并做相应的处理 onLayout() onTouch() smoothScrollTo(),当手指快速的时候切换菜单的状态利用GestureDetector 手势处理类:

1 /** 2 * description: 3 * 仿QQ6.0主页面侧滑的自定View 4 * Created by 曾辉 on 2016/11/1. 5 * QQ:240336124 6 * Email: 240336124@qq.com 7 * Version:1.0 8 */ 9public class SlidingMenu extends HorizontalScrollView { 10 private View mMenuView; 11 private View mContentView; 12 private int mMenuWidth; 13 // 手势处理类 主要用来处理手势快速滑动 14 private GestureDetector mGestureDetector; 15 16 // 菜单是否打开 17 private boolean mMenuIsOpen = false; 18 19 public SlidingMenu(Context context) { 20 this(context, null); 21 } 22 23 public SlidingMenu(Context context, AttributeSet attrs) { 24 this(context, attrs, 0); 25 } 26 27 public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) { 28 super(context, attrs, defStyleAttr); 29 // 获取自定义的右边留出的宽度 30 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu); 31 float rightPadding = array.getDimension( 32 R.styleable.SlidingMenu_rightPadding, dip2px(50)); 33 // 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度 34 mMenuWidth = (int) (getScreenWidth() - rightPadding); 35 array.recycle(); 36 37 // 实例化手势处理类 38 mGestureDetector = new GestureDetector(context,new GestureListener()); 39 } 40 41 /** 42 * 把dip 转成像素 43 */ 44 private float dip2px(int dip) { 45 return TypedValue.applyDimension( 46 TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics()); 47 } 48 49 50 @Override 51 protected void onFinishInflate() { 52 super.onFinishInflate(); 53 54 // 1.获取根View也就是外层的LinearLayout 55 ViewGroup container = (ViewGroup) this.getChildAt(0); 56 57 int containerChildCount = container.getChildCount(); 58 if (containerChildCount > 2) { 59 // 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局) 60 throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局"); 61 } 62 63 // 2.获取菜单和内容布局 64 mMenuView = container.getChildAt(0); 65 mContentView = container.getChildAt(1); 66 67 // 3.指定内容和菜单布局的宽度 68 // 3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度 69 mMenuView.getLayoutParams().width = mMenuWidth; 70 // 3.2 内容的宽度 = 屏幕的宽度 71 mContentView.getLayoutParams().width = getScreenWidth(); 72 } 73 74 @Override 75 public boolean onTouchEvent(MotionEvent ev) { 76 77 // 处理手指快速滑动 78 if(mGestureDetector.onTouchEvent(ev)){ 79 return mGestureDetector.onTouchEvent(ev); 80 } 81 82 switch (ev.getAction()) { 83 case MotionEvent.ACTION_UP: 84 // 手指抬起获取滚动的位置 85 int currentScrollX = getScrollX(); 86 if (currentScrollX > mMenuWidth / 2) { 87 // 关闭菜单 88 closeMenu(); 89 } else { 90 // 打开菜单 91 openMenu(); 92 } 93 return false; 94 } 95 return super.onTouchEvent(ev); 96 } 97 98 /** 99 * 打开菜单 100 */ 101 private void openMenu() { 102 smoothScrollTo(0, 0); 103 mMenuIsOpen = true; 104 } 105 106 /** 107 * 关闭菜单 108 */ 109 private void closeMenu() { 110 smoothScrollTo(mMenuWidth, 0); 111 mMenuIsOpen = false; 112 } 113 114 @Override 115 protected void onLayout(boolean changed, int l, int t, int r, int b) { 116 super.onLayout(changed, l, t, r, b); 117 // 布局指定后会从新摆放子布局,当其摆放完毕后,让菜单滚动到不可见状态 118 if (changed) { 119 scrollTo(mMenuWidth, 0); 120 } 121 } 122 123 /** 124 * 获取屏幕的宽度 125 */ 126 public int getScreenWidth() { 127 Resources resources = this.getResources(); 128 DisplayMetrics dm = resources.getDisplayMetrics(); 129 return dm.widthPixels; 130 } 131 132 133 private class GestureListener extends GestureDetector.SimpleOnGestureListener{ 134 @Override 135 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 136 // 当手指快速滑动时候回调的方法 137 Log.e("TAG",velocityX+""); 138 // 如果菜单打开 并且是向左快速滑动 切换菜单的状态 139 if(mMenuIsOpen){ 140 if(velocityX<-500){ 141 toggleMenu(); 142 return true; 143 } 144 }else{ 145 // 如果菜单关闭 并且是向右快速滑动 切换菜单的状态 146 if(velocityX>500){ 147 toggleMenu(); 148 return true; 149 } 150 } 151 152 return false; 153 } 154 } 155 156 /** 157 * 切换菜单的状态 158 */ 159 private void toggleMenu() { 160 if(mMenuIsOpen){ 161 closeMenu(); 162 }else{ 163 openMenu(); 164 } 165 } 166}

到了这一步之后我们就可以切换菜单了,并且处理了手指快速滑动,迫不及待的看下效果

这里写图片描述

2.6. 实现菜单左边抽屉样式的动画效果,监听滚动回调的方法onScrollChanged() 这个就非常简单了一句话就搞定,效果就不看了一起看终极效果吧

1@Override 2 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 3 super.onScrollChanged(l, t, oldl, oldt); 4 5 // l 是 当前滚动的x距离 在滚动的时候会不断反复的回调这个方法 6 Log.e(TAG,l+""); 7 8 mMenuView.setTranslationX(l*0.8f); 9 }

2.7. 实现菜单右边菜单的阴影透明度效果,这个打算在主页面内容布局上面加一层阴影,用ImageView即可,那么我们的内容View就需要换了

1/** 2 * description: 3 * 仿QQ6.0主页面侧滑的自定View 4 * Created by 曾辉 on 2016/11/1. 5 * QQ:240336124 6 * Email: 240336124@qq.com 7 * Version:1.0 8 */ 9public class SlidingMenu extends HorizontalScrollView { 10 private static final String TAG = "HorizontalScrollView"; 11 private Context mContext; 12 13 // 4.给菜单和内容View指定宽高 - 左边菜单View 14 private View mMenuView; 15 16 // 4.给菜单和内容View指定宽高 - 菜单的宽度 17 private int mMenuWidth; 18 // 5.3 手势处理类 主要用来处理手势快速滑动 19 private GestureDetector mGestureDetector; 20 // 5.3 菜单是否打开 21 private boolean mMenuIsOpen = false; 22 23 // 7(4). 主页面内容View的布局包括阴影ImageView 24 private ViewGroup mContentView; 25 // 7.给内容添加阴影效果 - 阴影的ImageView 26 private ImageView mShadowIv; 27 28 public SlidingMenu(Context context) { 29 this(context, null); 30 } 31 32 public SlidingMenu(Context context, AttributeSet attrs) { 33 this(context, attrs, 0); 34 } 35 36 public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) { 37 super(context, attrs, defStyleAttr); 38 //4.1 计算左边菜单的宽度 39 //4.1.1 获取自定义的右边留出的宽度 40 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu); 41 float rightPadding = array.getDimension( 42 R.styleable.SlidingMenu_rightPadding, dip2px(50)); 43 // 4.1.2 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度 44 mMenuWidth = (int) (getScreenWidth() - rightPadding); 45 array.recycle(); 46 47 // 5.3 实例化手势处理类 48 mGestureDetector = new GestureDetector(context,new GestureListener()); 49 50 this.mContext = context; 51 } 52 53 /** 54 * 把dip 转成像素 55 */ 56 private float dip2px(int dip) { 57 return TypedValue.applyDimension( 58 TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics()); 59 } 60 61 62 @Override 63 protected void onFinishInflate() { 64 super.onFinishInflate(); 65 // 4.2 指定菜单和内容View的宽度 66 // 4.2.1.获取根View也就是外层的LinearLayout 67 ViewGroup container = (ViewGroup) this.getChildAt(0); 68 69 int containerChildCount = container.getChildCount(); 70 if (containerChildCount > 2) { 71 // 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局) 72 throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局"); 73 } 74 75 // 4.2.2.获取菜单和内容布局 76 mMenuView = container.getChildAt(0); 77 78 // 7.给内容添加阴影效果 79 // 7.1 先new一个主内容布局用来放 阴影和LinearLayout原来的内容布局 80 mContentView = new FrameLayout(mContext); 81 ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams( 82 ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); 83 84 // 7.2 获取原来的内容布局,并把原来的内容布局从LinearLayout中异常 85 View oldContentView = container.getChildAt(1); 86 container.removeView(oldContentView); 87 88 // 7.3 把原来的内容View 和 阴影加到我们新创建的内容布局中 89 mContentView.addView(oldContentView); 90 // 7.3.1 创建阴影ImageView 91 mShadowIv = new ImageView(mContext); 92 mShadowIv.setBackgroundColor(Color.parseColor("#99000000")); 93 mContentView.addView(mShadowIv); 94 95 // 7.4 把包含阴影的新的内容View 添加到 LinearLayout中 96 container.addView(mContentView); 97 98 // 4.2.3.指定内容和菜单布局的宽度 99 // 4.2.3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度 100 mMenuView.getLayoutParams().width = mMenuWidth; 101 // 4.2.3.2 内容的宽度 = 屏幕的宽度 102 mContentView.getLayoutParams().width = getScreenWidth(); 103 } 104 105 /** 106 * 5.处理手指抬起和快速滑动切换菜单 107 */ 108 @Override 109 public boolean onTouchEvent(MotionEvent ev) { 110 // 5.3 处理手指快速滑动 111 if(mGestureDetector.onTouchEvent(ev)){ 112 return mGestureDetector.onTouchEvent(ev); 113 } 114 115 switch (ev.getAction()) { 116 case MotionEvent.ACTION_UP: 117 // 5.1 手指抬起获取滚动的位置 118 int currentScrollX = getScrollX(); 119 if (currentScrollX > mMenuWidth / 2) { 120 // 5.1.1 关闭菜单 121 closeMenu(); 122 } else { 123 // 5.1.2 打开菜单 124 openMenu(); 125 } 126 return false; 127 } 128 return super.onTouchEvent(ev); 129 } 130 131 @Override 132 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 133 super.onScrollChanged(l, t, oldl, oldt); 134 135 // l 是 当前滚动的x距离 在滚动的时候会不断反复的回调这个方法 136 Log.e(TAG,l+""); 137 // 6. 实现菜单左边抽屉样式的动画效果 138 mMenuView.setTranslationX(l*0.8f); 139 140 // 7.给内容添加阴影效果 - 计算梯度值 141 float gradientValue = l * 1f / mMenuWidth;// 这是 1 - 0 变化的值 142 143 // 7.给内容添加阴影效果 - 给阴影的View指定透明度 0 - 1 变化的值 144 float shadowAlpha = 1 - gradientValue; 145 mShadowIv.setAlpha(shadowAlpha); 146 } 147 148 /** 149 * 5.1.2 打开菜单 150 */ 151 private void openMenu() { 152 smoothScrollTo(0, 0); 153 mMenuIsOpen = true; 154 } 155 156 /** 157 * 5.1.1 关闭菜单 158 */ 159 private void closeMenu() { 160 smoothScrollTo(mMenuWidth, 0); 161 mMenuIsOpen = false; 162 } 163 164 @Override 165 protected void onLayout(boolean changed, int l, int t, int r, int b) { 166 super.onLayout(changed, l, t, r, b); 167 // 布局指定后会从新摆放子布局,当其摆放完毕后,让菜单滚动到不可见状态 168 if (changed) { 169 scrollTo(mMenuWidth, 0); 170 } 171 } 172 173 /** 174 * 获取屏幕的宽度 175 */ 176 public int getScreenWidth() { 177 Resources resources = this.getResources(); 178 DisplayMetrics dm = resources.getDisplayMetrics(); 179 return dm.widthPixels; 180 } 181 182 183 /** 184 * 5.3 处理手指快速滑动 185 */ 186 private class GestureListener extends GestureDetector.SimpleOnGestureListener{ 187 @Override 188 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 189 // 当手指快速滑动时候回调的方法 190 Log.e(TAG,velocityX+""); 191 // 5.3.1 如果菜单打开 并且是向左快速滑动 切换菜单的状态 192 if(mMenuIsOpen){ 193 if(velocityX<0){ 194 toggleMenu(); 195 return true; 196 } 197 }else{ 198 // 5.3.2 如果菜单关闭 并且是向右快速滑动 切换菜单的状态 199 if(velocityX>0){ 200 toggleMenu(); 201 return true; 202 } 203 } 204 return false; 205 } 206 } 207 208 /** 209 * 切换菜单的状态 210 */ 211 private void toggleMenu() { 212 if(mMenuIsOpen){ 213 closeMenu(); 214 }else{ 215 openMenu(); 216 } 217 } 218}

我们来看一下最后的效果吧,最终代码量并不是很多。

这里写图片描述

附视频地址:http://pan.baidu.com/s/1dEA0NLZ

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Android 6.0 运行时权限封装框架

1.概述视频讲解都已经录了好几天,但是最近实在抽不开身一直在忙Unity3D,就连光棍节都在写,上次我们只是写了,但是并未对其做代码封装,这一次我们做一个彻底的处理。  附视频讲解地址:  这里写图片描述2.框架封装2.1.简单例子publicclassMainActivityextendsAppCompatActivity

Taro小程序自定义顶部导航栏

微信自带的顶部导航栏是无法支持自定义icon和增加元素的,在开发小程序的时候自带的根本满足不了需求,分享一个封装好的组件,支持自定义icon、扩展dom,适配安卓、ios、h5,全面屏。我用的是京东的Taro多端编译框架写的小程序,原生的也可以适用,用到的微信/taro的api做调整就行,实现效果如下。!在这里插入图片描述(https://i