Android仿QQ6.0主页面侧滑效果
作者:z240336124 发布时间:2022-08-06 03:44:16
1.概述
最近一直都在带实习生做项目,发现自己好久没有写博客了,这几天更新会比较频繁,今天玩QQ的时候发现QQ主页菜单滑动效果早就变了,实在忍不住晚上就来实现一下了!
2.实现
2.1. 实现的方式多种多样
2.1.1自定义ViewGroup ,处理其onTouch事件
2.1.2FrameLayout + 手势处理类GestureDetector
2.2.3使用Google自带的DrawerLayout 对其进行修改
2.2.4继承自水平滚动HorizontalScrollView
大家可以看一下这个http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0909/6612.html,这种方式继承自ViewGroup,个人觉得还行但是还是比较繁琐要处理的东西也比较多,那么我就用最后一种2.2.4的方式实现,有人说直接去网上下载一个源代码就可以了,这我就只能GG了。
2.3. 自定义SlidingMenu extends HorizontalScrollView 然后写好布局文件这个和ScrollView的用法一样,只不过是横向滚动的
/**
* description:
* 仿QQ5.0主页面侧滑的自定View
* Created by 曾辉 on 2016/11/1.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
public SlidingMenu(Context context) {
super(context);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2.4. 运行起来之后发现布局不对,完全乱了明明都是match_parent可是就是不行那么我们就需要用代码指定菜单和内容的宽度:
菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度
内容的宽度 = 屏幕的宽度
/**
* description:
* 仿QQ5.0主页面侧滑的自定View
* Created by 曾辉 on 2016/11/1.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private View mMenuView;
private View mContentView;
private int mMenuWidth;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义的右边留出的宽度
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding,dip2px(50));
// 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
}
/**
* 把dip 转成像素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 1.获取根View也就是外层的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if(containerChildCount>2){
// 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局)
throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局");
}
// 2.获取菜单和内容布局
mMenuView = container.getChildAt(0);
mContentView = container.getChildAt(1);
// 3.指定内容和菜单布局的宽度
// 3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度
mMenuView.getLayoutParams().width = mMenuWidth;
// 3.2 内容的宽度 = 屏幕的宽度
mContentView.getLayoutParams().width = getScreenWidth();
}
/**
* 获取屏幕的宽度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
}
目前的效果就是可以滑动,并且菜单和主页面内容的布局宽度正常
2.5 接下来一开始就让菜单滑动到关闭状态,手指滑动抬起判断菜单打开和关闭并做相应的处理 onLayout() onTouch() smoothScrollTo(),当手指快速的时候切换菜单的状态利用GestureDetector 手势处理类:
/**
* description:
* 仿QQ5.0主页面侧滑的自定View
* Created by 曾辉 on 2016/11/1.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private View mMenuView;
private View mContentView;
private int mMenuWidth;
// 手势处理类 主要用来处理手势快速滑动
private GestureDetector mGestureDetector;
// 菜单是否打开
private boolean mMenuIsOpen = false;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义的右边留出的宽度
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding, dip2px(50));
// 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
// 实例化手势处理类
mGestureDetector = new GestureDetector(context,new GestureListener());
}
/**
* 把dip 转成像素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 1.获取根View也就是外层的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if (containerChildCount > 2) {
// 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局)
throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局");
}
// 2.获取菜单和内容布局
mMenuView = container.getChildAt(0);
mContentView = container.getChildAt(1);
// 3.指定内容和菜单布局的宽度
// 3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度
mMenuView.getLayoutParams().width = mMenuWidth;
// 3.2 内容的宽度 = 屏幕的宽度
mContentView.getLayoutParams().width = getScreenWidth();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 处理手指快速滑动
if(mGestureDetector.onTouchEvent(ev)){
return mGestureDetector.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 手指抬起获取滚动的位置
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
// 关闭菜单
closeMenu();
} else {
// 打开菜单
openMenu();
}
return false;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单
*/
private void openMenu() {
smoothScrollTo(0, 0);
mMenuIsOpen = true;
}
/**
* 关闭菜单
*/
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0);
mMenuIsOpen = false;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 布局指定后会从新摆放子布局,当其摆放完毕后,让菜单滚动到不可见状态
if (changed) {
scrollTo(mMenuWidth, 0);
}
}
/**
* 获取屏幕的宽度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
private class GestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 当手指快速滑动时候回调的方法
Log.e("TAG",velocityX+"");
// 如果菜单打开 并且是向左快速滑动 切换菜单的状态
if(mMenuIsOpen){
if(velocityX<-500){
toggleMenu();
return true;
}
}else{
// 如果菜单关闭 并且是向右快速滑动 切换菜单的状态
if(velocityX>500){
toggleMenu();
return true;
}
}
return false;
}
}
/**
* 切换菜单的状态
*/
private void toggleMenu() {
if(mMenuIsOpen){
closeMenu();
}else{
openMenu();
}
}
}
到了这一步之后我们就可以切换菜单了,并且处理了手指快速滑动,迫不及待的看下效果
2.6. 实现菜单左边抽屉样式的动画效果,监听滚动回调的方法onScrollChanged() 这个就非常简单了一句话就搞定,效果就不看了一起看终极效果吧
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// l 是 当前滚动的x距离 在滚动的时候会不断反复的回调这个方法
Log.e(TAG,l+"");
mMenuView.setTranslationX(l*0.8f);
}
2.7. 实现菜单右边菜单的阴影透明度效果,这个打算在主页面内容布局上面加一层阴影,用ImageView即可,那么我们的内容View就需要换了
/**
* description:
* 仿QQ5.0主页面侧滑的自定View
* Created by 曾辉 on 2016/11/1.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private static final String TAG = "HorizontalScrollView";
private Context mContext;
// 4.给菜单和内容View指定宽高 - 左边菜单View
private View mMenuView;
// 4.给菜单和内容View指定宽高 - 菜单的宽度
private int mMenuWidth;
// 5.3 手势处理类 主要用来处理手势快速滑动
private GestureDetector mGestureDetector;
// 5.3 菜单是否打开
private boolean mMenuIsOpen = false;
// 7(4). 主页面内容View的布局包括阴影ImageView
private ViewGroup mContentView;
// 7.给内容添加阴影效果 - 阴影的ImageView
private ImageView mShadowIv;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//4.1 计算左边菜单的宽度
//4.1.1 获取自定义的右边留出的宽度
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding, dip2px(50));
// 4.1.2 计算菜单的宽度 = 屏幕的宽度 - 自定义右边留出的宽度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
// 5.3 实例化手势处理类
mGestureDetector = new GestureDetector(context,new GestureListener());
this.mContext = context;
}
/**
* 把dip 转成像素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 4.2 指定菜单和内容View的宽度
// 4.2.1.获取根View也就是外层的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if (containerChildCount > 2) {
// 里面只允许放置两个布局 一个是Menu(菜单布局) 一个是Content(主页内容布局)
throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允许两个布局,菜单布局和主页内容布局");
}
// 4.2.2.获取菜单和内容布局
mMenuView = container.getChildAt(0);
// 7.给内容添加阴影效果
// 7.1 先new一个主内容布局用来放 阴影和LinearLayout原来的内容布局
mContentView = new FrameLayout(mContext);
ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
// 7.2 获取原来的内容布局,并把原来的内容布局从LinearLayout中异常
View oldContentView = container.getChildAt(1);
container.removeView(oldContentView);
// 7.3 把原来的内容View 和 阴影加到我们新创建的内容布局中
mContentView.addView(oldContentView);
// 7.3.1 创建阴影ImageView
mShadowIv = new ImageView(mContext);
mShadowIv.setBackgroundColor(Color.parseColor("#99000000"));
mContentView.addView(mShadowIv);
// 7.4 把包含阴影的新的内容View 添加到 LinearLayout中
container.addView(mContentView);
// 4.2.3.指定内容和菜单布局的宽度
// 4.2.3.1 菜单的宽度 = 屏幕的宽度 - 自定义的右边留出的宽度
mMenuView.getLayoutParams().width = mMenuWidth;
// 4.2.3.2 内容的宽度 = 屏幕的宽度
mContentView.getLayoutParams().width = getScreenWidth();
}
/**
* 5.处理手指抬起和快速滑动切换菜单
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 5.3 处理手指快速滑动
if(mGestureDetector.onTouchEvent(ev)){
return mGestureDetector.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 5.1 手指抬起获取滚动的位置
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
// 5.1.1 关闭菜单
closeMenu();
} else {
// 5.1.2 打开菜单
openMenu();
}
return false;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// l 是 当前滚动的x距离 在滚动的时候会不断反复的回调这个方法
Log.e(TAG,l+"");
// 6. 实现菜单左边抽屉样式的动画效果
mMenuView.setTranslationX(l*0.8f);
// 7.给内容添加阴影效果 - 计算梯度值
float gradientValue = l * 1f / mMenuWidth;// 这是 1 - 0 变化的值
// 7.给内容添加阴影效果 - 给阴影的View指定透明度 0 - 1 变化的值
float shadowAlpha = 1 - gradientValue;
mShadowIv.setAlpha(shadowAlpha);
}
/**
* 5.1.2 打开菜单
*/
private void openMenu() {
smoothScrollTo(0, 0);
mMenuIsOpen = true;
}
/**
* 5.1.1 关闭菜单
*/
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0);
mMenuIsOpen = false;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 布局指定后会从新摆放子布局,当其摆放完毕后,让菜单滚动到不可见状态
if (changed) {
scrollTo(mMenuWidth, 0);
}
}
/**
* 获取屏幕的宽度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
/**
* 5.3 处理手指快速滑动
*/
private class GestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 当手指快速滑动时候回调的方法
Log.e(TAG,velocityX+"");
// 5.3.1 如果菜单打开 并且是向左快速滑动 切换菜单的状态
if(mMenuIsOpen){
if(velocityX<0){
toggleMenu();
return true;
}
}else{
// 5.3.2 如果菜单关闭 并且是向右快速滑动 切换菜单的状态
if(velocityX>0){
toggleMenu();
return true;
}
}
return false;
}
}
/**
* 切换菜单的状态
*/
private void toggleMenu() {
if(mMenuIsOpen){
closeMenu();
}else{
openMenu();
}
}
}
我们来看一下最后的效果吧,最终代码量不是很多啦,需要的请下载源码,如果是实现QQ5.0或是酷狗的侧滑效果可以自己改改。


猜你喜欢
- @Cacheable自定义KeyGenerator1. 概述SpringBoot 使用 @Cacheable 可以方便的管理缓存数据,在不指
- 文档合并是一种高效文档处理方式。如果能够有一个方法能将多种不同类型的文档合并成一种文档格式,那么在文档存储管理上将为我们提供极大的便利。因此
- 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序
- C#语言有很多值得学习的地方,这里我们主要介绍C#使用if语句。如果想根据一个布尔表达式的结果选择执行两个不同的代码块,就可以C#使用if语
- 本章主要建立在已经安装好Erlang以及RabbitMQ的基础上,接下来,简单介绍一下使用一、Direct直接模式 通过routingKey
- 质数又称素数。一个大于1的自然数,如果除了1和它自身外,不能被其他自然数整除的数;否则称为合数。根据算术基本定理,每一个比1大的整数,要么本
- 前言前一段时间做了一个项目,需要解决中文、繁体、英文的国际化问题,所以本文将详细介绍springboot页面国际化配置的过程方法如下1.引入
- 这篇文章主要介绍了Java如何利用return结束方法调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 如果还不知道DecorView,那也没有什么关系 ^_^先来看看实现的效果实现的大致思路首先需要明白什么是DecorView,他是andro
- 主要实现的功能:1.程序附带多张拼图随机拼图。2.可手动添加拼图。3.游戏成功判断。4.30秒超时判断。 Puzzle.csus
- 今天启动springboot项目时失败了解决检查原因发现是启动类的MapperScan("")的值写到类名了,改成类所在
- 结构型设计模式创建型设计模式主要是为了解决创建对象的问题,而结构型设计模式则是为了解决已有对象的使用问题。适配器模式适配器模式比较好理解,因
- 1.概述在本快速教程中,我们将学习如何设置Spring Security LDAP。在我们开始之前,了解一下LDAP是什么? - 它代表轻量
- TransactionTemplate的使用总结:在类中注入TransactionTemplate,即可在springboot中使用编程式事
- 前言本次示例代码的文件结构如下图所示。1. 导入依赖坐标在 order-service 的 pom.xml 文件中导入 Feign 的依赖坐
- 应用场景假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费如果仓库中没有产品,则生产者将产品放入仓库,否
- 1、数组理论基础数组是存放在连续内存空间上的相同类型数据的集合,可以通过下标索引的方式获取到下标下对应的数据。举个栗子(字符数组)~可以看到
- 小编今天研究了在Unity3D中的数据持久化问题。数据持久化在任何一个开发领域都是一个值得关注的问题,小到一个应用中配置文件的读写,大到数据
- Java 中的内部类这是一个 Java 内部类的简单实现:public class OutterJava { pr
- 这段时间想到一个有趣的功能,就是在Android的代码编译期间进行一些骚操作,来达到一些日常情境下难以实现的功能,比如监听应用中的所有onC