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 ...
随机推荐
- Struts2 源码分析——前言
笔者简言 笔者在博园里面注册是在二年前.可是那个时候我不知道要写些什么,也怕写出来被别人骂误人子弟.而现在却动笔了是因为前一段时间内我去参加一些大公司的面试,让笔者内心深处留下很多问号.最近三年来我一 ...
- JavaScript基础—插曲
Javascript基础 1:js中我们最好使用单引号,其实可以使用双引号的但是为了区别所以js中全部使用单引号.注释和C#的是一样的.网页里面的执行顺序是从上到下依次执行的,不管你js放到哪里,都会 ...
- WPF学习之绘图和动画
如今的软件市场,竞争已经进入白热化阶段,功能强.运算快.界面友好.Bug少.价格低都已经成为了必备条件.这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽.是否能通过动画.3D等效果是 ...
- Tarjan算法---强联通分量
1.基础知识 在有向图G,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子 ...
- echarts 折线图动态x轴及数据
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...
- jquery分隔Url的param方法
最近需要分隔url的querystring,用到了特意记录一下.方法: //获取url中的paramsvar search = location.search.substring(1);//param ...
- 背水一战 Windows 10 (10) - 资源: StaticResource, ThemeResource
[源码下载] 背水一战 Windows 10 (10) - 资源: StaticResource, ThemeResource 作者:webabcd 介绍背水一战 Windows 10 之 资源 St ...
- Angular 2 要来了,Wijmo 已准备好迎接
Angular 是一款优秀的前端JS框架,已被用于Google的多款产品中,其核心特点是:MVVM.模块化.自动化双向数据绑定.语义化标签.依赖注入等.6年过去了,Angular 迎来了2.0版本. ...
- 【Java每日一题】201612015
package Dec2016; import java.util.HashSet; public class Ques1205 { public static void main(String[] ...
- 2015暑假多校联合---Zero Escape(变化的01背包)
题目链接 http://acm.hust.edu.cn/vjudge/contest/130883#problem/C Problem Description Zero Escape, is a vi ...