DragLayout: QQ5.0侧拉菜单的新特效
一、项目概要
1.1 项目效果如图:
1.2 需要使用到的技术
ViewDragHelper: 要实现和QQ5.0侧滑的特效,需要借助谷歌在2013年I/O大会上发布的ViewDragHelper类,提供这个类目的就是为了解决拖拽滑动问题
1.3 侧滑菜单的实现方式
1. SlidingMenu 第三方库
2. DrawerLayout v4包中的类
3. 自定义控件
1.4 一些回调方法
- tryCaptureView: 用来决定是否可以拖动
- clampViewPositionHorizontal: 用来设置子控件将要显示的位置 [限制子控件拖动的范围]
- getViewHorizontalDragRange:返回水平方向拖动的最大范围,返回大于0的值才可以拖动
- onViewPositionChanged: 位置改变时调用 [关联菜单与主界面的滑动,监听拖动状态,伴随动画]
- onViewReleased: 拖动结束后,松开手时调用 [平滑地打开或关闭侧滑菜单]
二、项目实现
2.1 创建DragLayout
public class DragLayout extends FrameLayout {
public DragLayout(Context context) {
super(context);
}
public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
2.2 创建侧滑面板布局
<com.xiaowu.draglayout.view.DragLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drag_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg" > <!-- 侧滑菜单布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#33ff0000" /> <!-- 主界面布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#3300ff00" /> </com.xiaowu.draglayout.view.DragLayout>
2.3 DragLayout的主程序代码,下面代码中有详细的讲解,我就不多分步骤实现了
package com.xiaowu.draglayout.view; import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout; /**
* Created by ${VINCENT} on 2016/11/8.
*/ public class DragLayout extends FrameLayout { private ViewDragHelper mViewDragHelper;
private View mMenuView;
private View mMainView;
private int mRange;
private int mWidth;
private int mHeight; public DragLayout(Context context) {
super(context);
init();
} public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} /** 填充完成后调用此方法 */
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 健壮性判断
if (getChildCount() < 2) {
throw new IllegalStateException("DrawLayout至少要有两个子控件");
}
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
} // step1:创建ViewDragHelper对象
private void init() {
float sensitivity = 1.0f; //值越大,灵敏度越高
mViewDragHelper = ViewDragHelper.create(this, sensitivity, mCallBack);
} // step2:由ViewDragHelper决定是否拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
} // step3:把触摸事件交给ViewDragHelper处理
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true; //让mViewDragHelper持续接收到触摸事件
} // step4:处理ViewDragHelper的Callback方法
ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() { // (1)捕获子控件,返回true表示子控件可以拖动
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
} // (2)子控件显示的方向(horizontal, vertical)
// left: 被拖动控件的将要显示的位置
// dx: 位置的偏移量 = left - 当前的left
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mMainView) {
left = reviseLeft(left);
}
return left;
} // (3)返回水平方向拖动的最大范围mRange,内部会根据返回值计算动画执行的时间
@Override
public int getViewHorizontalDragRange(View child) {
return mRange;
} // (4)位置发生改变的回调
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
// (a) 关联子控件的滑动
if (changedView == mMenuView) {
// 侧拉菜单界面不变时
mMenuView.layout(0, 0, mWidth, mHeight);
// 主菜单界面的新位置
int newLeft = mMenuView.getLeft() + dx;
newLeft = reviseLeft(newLeft);
mMainView.layout(newLeft, 0, mWidth + newLeft, mHeight);
}
// (b) 事件的监听(打开,拖动,关闭)
listenDragStatus();
// (c) 事件伴随的动画
animateChildren();
} // (5) 拖动结束时回调的方法
// xvel:释放时的回调速度,在这里向右为正
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel > 0) {
open();
} else if (xvel == 0 && mMainView.getLeft() > mRange / 2) {
open();
} else {
close();
}
}
}; //============================动画的定义=====================================
/** 估值器:变化值 = 开始值 + (结束值 - 开始值) * 百分比 */
public float evaluate(float start, float end, float percent) {
return start + (end - start) * percent;
} protected void animateChildren() {
float percent = ((float) mMainView.getLeft()) / mRange; // 1.主界面的缩放
mMainView.setScaleX(evaluate(1f, 0.8f, percent));
mMainView.setScaleY(evaluate(1f, 0.8f, percent));
// 2.侧拉菜单的缩放
mMenuView.setTranslationX((int) evaluate(-mRange, 0, percent)); // 平移
mMenuView.setScaleX(evaluate(0.5f, 1.0f, percent));
mMenuView.setScaleY(evaluate(0.5f, 1.0f, percent));
mMenuView.setAlpha(evaluate(0.5f, 1.0f, percent));
// 3.背景图片:亮度的变化
Drawable background = getBackground();
if (background != null) {
// 过渡的颜色
int color = (int)evaluate2(percent, Color.BLACK, Color.TRANSPARENT);
background.setColorFilter(color, PorterDuff.Mode.SRC_OVER);
}
} /** 处理颜色渐变的兼容性问题 */
public Object evaluate2(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff; int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff; return ((startA + (int)(fraction * (endA - startA))) << 24) |
((startR + (int)(fraction * (endR - startR))) << 16) |
((startG + (int)(fraction * (endG - startG))) << 8) |
((startB + (int)(fraction * (endB - startB))));
} //============================状态的监听begin================================
/** 事件的监听 */
protected void listenDragStatus() {
int left = mMainView.getLeft();
if (left == 0) {
mCurrentStatus = DragStatus.CLOSE;
} else if (left == mRange) {
mCurrentStatus = DragStatus.OPEN;
} else {
mCurrentStatus = DragStatus.DRAGGING;
} //当事件发生时,调用监听器中的方法
if (mOnDragListener != null) {
if (mCurrentStatus == DragStatus.OPEN) {
mOnDragListener.onOpen();
} else if (mCurrentStatus == DragStatus.CLOSE) {
mOnDragListener.onClose();
} else {
float percent = ((float) mMainView.getLeft()) / mRange;
mOnDragListener.onDragging(percent);
}
}
} /** 状态的定义 */
public enum DragStatus {
OPEN, CLOSE, DRAGGING
} /** 当前的状态 */
private DragStatus mCurrentStatus = DragStatus.CLOSE; public DragStatus getCurrentStatus() {
return mCurrentStatus;
} /** 定义接口 */
public interface OnDragListener {
void onOpen();
void onClose();
void onDragging(float percent);
} private OnDragListener mOnDragListener; /** 提供设置监听器的set方法 */
public void setOnDragListener(OnDragListener onDragListener) {
this.mOnDragListener = onDragListener;
} //============================状态的监听end================================ @Override
public void computeScroll() {
super.computeScroll();
// 若如果没有移动到正确的位置,需要刷新
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} /** 限定主界面的滑动范围 */
protected int reviseLeft(int left) {
if (left < 0) {
left = 0;
} else if (left > mRange) {
left = mRange;
}
return left;
} /** 控件尺寸发生改变时,回调该方法 */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 获取DrawLayout的宽高
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
// 拖拽的比例
mRange = (int) (mWidth * 0.6f);
} /** 打开侧拉菜单 */
protected void open() {
mViewDragHelper.smoothSlideViewTo(mMainView, mRange, 0);
// 刷新界面
ViewCompat.postInvalidateOnAnimation(this);
} /** 关闭侧拉菜单 */
protected void close() {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
// 刷新界面
ViewCompat.postInvalidateOnAnimation(this);
} /** 侧滑菜单是否打开 */
public boolean isOpen() {
return mCurrentStatus == DragStatus.OPEN;
} }
2.4 创建MyLinearLayout.java文件,处理侧拉与主菜单的冲突事件
package com.xiaowu.draglayout.view; import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout; /**
* Created by ${VINCENT} on 2016/11/9.
*/ public class MyLinearLayout extends LinearLayout { private DragLayout mDragLayout; public MyLinearLayout(Context context) {
super(context);
} public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} /** 根据它的打开状态决定是否要拦截事件 */
public void setDragLayout(DragLayout dragLayout) {
this.mDragLayout = dragLayout;
} /** 如果侧滑菜单打开了,禁止主菜单的列表滑动 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mDragLayout.isOpen()) {
return true;
}
return super.onInterceptTouchEvent(ev);
} /** 如果侧滑菜单打开了,消费主菜单的触摸事件,禁止通过滑动主菜单使侧拉菜单的列表滑动 */
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mDragLayout.isOpen()) {
return true;
}
return super.onTouchEvent(event);
}
}
2.5 接下来是MainActivity的代码实现
package com.xiaowu.draglayout; import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast; import com.xiaowu.draglayout.view.DragLayout;
import com.xiaowu.draglayout.view.MyLinearLayout; public class MainActivity extends AppCompatActivity { private ImageView mIvHeader;
private MyLinearLayout mMyLinearLayout;
private DragLayout mDragLayout; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main);
mIvHeader = (ImageView) findViewById(R.id.iv_header); initDragLayout();
mMyLinearLayout = (MyLinearLayout) findViewById(R.id.my_ll);
// 根据打开的状态决定是否拦截事件
mMyLinearLayout.setDragLayout(mDragLayout); initListView();
} private void initListView() {
ListView lvMenu = (ListView) findViewById(R.id.lv_menu);
ListView lvMain = (ListView) findViewById(R.id.lv_main); lvMenu.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
Constant.MENUS){
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) super.getView(position, convertView, parent);
view.setTextSize(dp2px(16));
view.setTextColor(Color.WHITE);
return view;
}
}); lvMain.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
Constant.LIST_DATAS)); } private void initDragLayout() {
mDragLayout = (DragLayout) findViewById(R.id.drag_layout);
mDragLayout.setOnDragListener(new DragLayout.OnDragListener() {
@Override
public void onOpen() {
showToast("打开");
} @Override
public void onClose() {
showToast("关闭");
} @Override
public void onDragging(float percent) {
mIvHeader.setAlpha(1 - percent );
}
});
} /** toast使用单例模式,可以随状态刷新 */
private Toast mToast; public void showToast(String msg) {
if (mToast == null) {
mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
}
mToast.setText(msg);
mToast.show();
} public int dp2px(int dp) {
float density = this.getResources().getDisplayMetrics().density;
return (int) (dp * density + 0.5f);
} }
2.6 布局文件的最终完善
<com.xiaowu.draglayout.view.DragLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drag_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"> <!-- 侧拉菜单界面 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:padding="15dp" > <ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/head"/> <ListView
android:id="@+id/lv_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp" /> </LinearLayout> <!-- 主菜单界面 -->
<com.xiaowu.draglayout.view.MyLinearLayout
android:id="@+id/my_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="#18B4ED" > <ImageView
android:id="@+id/iv_header"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:background="@drawable/head" />
</RelativeLayout> <ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" /> </com.xiaowu.draglayout.view.MyLinearLayout> </com.xiaowu.draglayout.view.DragLayout>
2.7 Constant静态类可以自己定义,不过我还是善良的贴了出来
package com.xiaowu.draglayout; public class Constant { /** 菜单列表数据 */
public static final String[] MENUS = new String[] {
"纸杯蛋糕[Cupcake]",
"甜甜圈[Donut]",
"闪电泡芙[Eclair]",
"冻酸奶[Froyo]",
"姜饼[Gingerbread]",
"蜂巢[Honeycomb]",
"冰淇淋三明治[Ice Cream Sandwich]",
"果冻豆[Jelly Bean]",
"奇巧[KitKat]",
"棒棒糖[Lollipop]",
"姜饼[Gingerbread]",
"蜂巢[Honeycomb]",
"冰淇淋三明治[Ice Cream Sandwich]",
"果冻豆[Jelly Bean]",
"奇巧[KitKat]",
"棒棒糖[Lollipop]",
"棉花糖[Marshmallow]"
}; /** 列表数据1 */
public static final String[] LIST_DATAS = {
"API1--1.0 [没有开发代号]",
"API2--1.1 Petit Four",
"API3--1.5 Cupcake",
"API4--1.6 Donut",
"API5--2.0 Eclair",
"API6--2.0.1 Eclair",
"API7--2.1 Eclair",
"API8--2.2 - 2.2.3 Froyo",
"API9--2.3 - 2.3.2 Gingerbread",
"API10--2.3.3-2.3.7 Gingerbread",
"API11--3.0 Honeycomb",
"API12--3.1 Honeycomb",
"API13--3.2 Honeycomb",
"API14--4.0 - 4.0.2 Ice Cream Sandwich",
"API15--4.0.3 - 4.0.4 Ice Cream Sandwich",
"API16--4.1 Jelly Bean",
"API17--4.2 Jelly Bean",
"API18--4.3 Jelly Bean",
"API19--4.4 KitKat",
"API20--4.4W",
"API21--5.0 Lollipop",
"API22--5.1 Lollipop",
"API23--6.0 Marshmallow"
};
}
三、一些可以借鉴的东西
*比如使用Toast的时候可以采用单例模式,使得Toast可以随时改变,而不会产生停顿延迟的问题(顶部效果图)
/** toast使用单例模式,可以随状态刷新 */
private Toast mToast; public void showToast(String msg) {
if (mToast == null) {
mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
}
mToast.setText(msg);
mToast.show();
}
四、提供给博友我的源代码
**下载链接:http://pan.baidu.com/s/1o8k4cZo 密码:m0fl
DragLayout: QQ5.0侧拉菜单的新特效的更多相关文章
- 使用DrawerLayout实现QQ5.0侧拉菜单效果
在上一篇文章中,我们介绍了怎么使用DrawerLayout来实现一个简单的侧拉菜单(使用DrawerLayout实现侧拉菜单),也就是我们常说的抽屉效果,GitHub上类似效果的实现方式非常多,实现出 ...
- 安卓开发笔记——关于开源项目SlidingMenu的使用介绍(仿QQ5.0侧滑菜单)
记得去年年末的时候写过这个侧滑效果,当时是利用自定义HorizontalScrollView来实现的,效果如下: 有兴趣的朋友可以看看这篇文件<安卓开发笔记——自定义HorizontalScro ...
- 【转】仿QQ5.0侧滑菜单ResideMenu
本文由孙国威 原创.如需转载,请注明出处! 原文:http://blog.csdn.net/manoel/article/details/39013095 为了后续对这个项目进行优化,比如透明度动画. ...
- 仿QQ5.0侧滑菜单
一.概述 侧滑菜单现在已经非常流行了,目前大概有这么几种:最普通的侧滑,抽屉侧滑,QQ侧滑 注:本文来自慕课网 二.最普通的侧滑 先上图 代码如下: public class MainActivity ...
- Android音乐播放器源码(歌词.均衡器.收藏.qq5.0菜单.通知)
一款Android音乐播放器源码,基本功能都实现了 qq5.0菜单(歌词.均衡器.收藏.qq5.0菜单.通知) 只有向右滑动出现,菜单键和指定按钮都还没有添加. 源码下载:http://code.66 ...
- QQ5.0左侧滑动显示效果
前三篇为大家介绍了如何实现简单的类QQ5.0左侧的侧滑效果,本篇我将带领大家一起探讨一下如何真正实现QQ5.0左侧的侧滑效果,对于本篇的内容与之前的三篇关联性很强,如果前三篇你已经完全掌握,对于这一篇 ...
- 安卓开发笔记——自定义HorizontalScrollView控件(实现QQ5.0侧滑效果)
对于滑动菜单栏SlidingMenu,大家应该都不陌生,在市场上的一些APP应用里经常可以见到,比如人人网,FaceBook等. 前段时间QQ5.0版本出来后也采用了这种设计风格:(下面是效果图) 之 ...
- 使用DrawerLayout实现侧拉菜单
侧拉菜单在android应用中非常常见,它的实现方式太多了,今天我们就说说使用Google提供的DrawerLayout来实现侧拉菜单效果,先来看张效果图: DrawerLayout的实现其实非常简单 ...
- Android 7.0(牛轧糖)新特性
Android 7.0(牛轧糖)新特性 谷歌正式在I/O大会现场详细介绍了有关Android 7.0的大量信息.目前,我们已经知道,新一代Android操作系统将支持无缝升级,能够通过Vulkan A ...
随机推荐
- LINQ to SQL语句(5)之Order By
适用场景:对查询出的语句进行排序,比如按时间排序等等. 说明:按指定表达式对集合排序:延迟,:按指定表达式对集合排序:延迟,默认是升序,加上descending表示降序,对应的扩展方法是OrderBy ...
- asp.net中Ajax控件的用途(一)
1,UpdatePanel控件,用户更新部分内容,示例 放入一个Label和一个Button,单击按钮,label显示当前时间. 2,ScriptManagerProxy控件,每个页面只能有一个Scr ...
- HTTPS能有效保护用户隐私
HTTPS就等于HTTP加上TLS(SSL),HTTPS协议的目标主要有三个: http://hovertree.com/menu/webfront/ 数据保密性.保证内容在传输过程中不会被第三方查看 ...
- Autofac Container 的简单的封装重构
为了使用方便,对Autofac container的简单封装,记录如下,备以后用或分享给大家,欢迎讨论! using Autofac; using Autofac.Core.Lifetime; usi ...
- js Date 时间格式化的扩展
js Date 时间格式化的扩展: Date.prototype.format = function (fmt) { var o = { , //月 "d+": this.getD ...
- 速战速决 (1) - PHP: 概述, 常量, 变量, 运算符, 表达式, 控制语句
[源码下载] 速战速决 (1) - PHP: 概述, 常量, 变量, 运算符, 表达式, 控制语句 作者:webabcd 介绍速战速决 之 PHP 概述 常量 变量 运算符 表达式 控制语句 示例1. ...
- 11.20 CSS定位智博星网页制作
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv=&qu ...
- 框架Spring笔记系列 一 基础
主题:Spring 1.什么样的问题,使得Spring框架应用而生?使用Spring解决了那些问题? 2.
- linux TCP: time wait bucket table overflow
早上一台rabbitmq和Java所在的服务器,客户端反馈超级卡,看io和cpu都不高.发现六七万消息挤压,临时性问题解决之后,看/var/log/messages,发现很多TCP: time wai ...
- 【转】超实用PHP函数总结整理
原文链接:http://www.codeceo.com/article/php-function.html 1.PHP加密解密 PHP加密和解密函数可以用来加密一些有用的字符串存放在数据库里,并且通过 ...