鹅厂系列一 : 仿QQ侧滑菜单
Posted z8z87878
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了鹅厂系列一 : 仿QQ侧滑菜单相关的知识,希望对你有一定的参考价值。
——不会的东西你不尝试的去做,你永远都不会做
好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧O(∩_∩)O~)
额,图片资源来自QQ_374.APK,里面四五千个图片,找这几个没把我累死,当然感谢QQ的资源,额,.先来看看初始布局,我不知道腾讯是怎么布局的,我自己为了做的像他们一点,我的布局暂时是像下面这样的,到了自定义控件的时候,还会进行重新测量和布局.
嗯,就是让左面板在主面板的下面,所以我们自定义的控件SlideLayout继承FrameLayout.一般自定义控件会涉及到三个方法,onMeasure 测量,onLayout布局,onDraw绘制,如果是继承ViewGroup的话我们一般需要重写布局方法,继承view的话要重写onDraw方法,当然你喜欢的话,都可以重写.我们这里继承的是FrameLayout,它继承的是ViewGroup,即它已经实现了布局方法,但是我说过了,我们要重新测量和布局,主要是对左面板slideView的测量和布局,我们要调整它的宽度和位置,所以我们先来重写onMeasure方法.onMeasure(int widthMeasureSpec, int heightMeasureSpec)看这两个参数,看名字就知道中式英语很像有没有,测量说明书,即我们能通过测量说明书来进行测量,其实这两个数是测量说明书上给定的规范值,所以叫测量规范,结合我们下面的代码进行讲解
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSlideView = getChildAt(0);
mMainView = getChildAt(1);
mWd_width = MeasureSpec.getSize(widthMeasureSpec); //使用测量说明书得到我们想要知道的宽度和高度值,它是match_parent的,所以拿到的就是窗体的宽高度
mWd_height = MeasureSpec.getSize(heightMeasureSpec);
int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作测量说明书规定值
int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
mSlideView.measure(sl_widthSpec,sl_heightSpec);
mMainView.measure(widthMeasureSpec,heightMeasureSpec); //主面板和自己一样,都是填充整个窗体,所以测量说明书的规定值一样
setMeasuredDimension(mWd_width, mWd_height); //测量自己用这个方法.
}
制作测量规范值的函数,第一个参数的意思是,你想要分配多少像素吧,没有给解释,这个不重要….重要的是第二个参数,第二个参数有三个值可以填,分别是UNSPECIFIED,EXACTLY,AT_MOST,我们看它们单词的意思就好理解了,第一个说不确定,就叫它自己看着办;第二个意思是精确的,即我们生活中确定以及肯定,它会禁它最大的努力去按第一个参数的值测量,你的值填的离谱,它也满足不了.第三个就和第二个挺像了,尽量,就是比精确的肯定性差一点.所以我们如果自己确定一个测量规范的话,我确定我要侧滑面板占屏幕的三分之二,所以我们这里用EXACTLY.测量好了,接着就是我们的布局了
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mSl_width = mSlideView.getWidth();
mSlideView.layout(mSlideView.getLeft() - mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
}
布局的时候,说明测量已经完成了,所以我们是可以拿到我们左面板的宽度的,然后执行layout(l,t,r,b)看参数就明白了,这个不讲了,我把它的left设为-自己看的三分之一,是自己为了做的像一点的想法.来看看我们重写测量布局后的效果
好了,这时候开始我们的拖动了.视图拖动我们用这个类ViewDragHelper,视图拖动帮助者,首选看看怎么得到它的对象static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)它是个静态方法,所以我们不是new出来的,第一个参数说拖动VIEW的父容器是谁,因为我们拖动的是主面板和左面板,所以他们的父容器是自己,即填this,第二个参数是灵敏度,它还有一个重载函数,只有两个参数,这个参数没写默认是1.0f,所以我们一般也填1.0f.第三个参数是ViewDragHelper 的内部类Callback ,我们需要写个类继承它.
class MyDragCallBack extends ViewDragHelper.Callback {
@Override //表示要捕获那个孩子,即处理哪个孩子的拖到事件,返回false不处理
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
这个是抽象方法,我们必须实现,因为两个孩子都要拖动,所以直接返回true,要像横向拖动,我们还得重写它的一个方法
@Override //
public int clampViewPositionHorizontal(View child, int left, int dx) { //left相对于屏幕左侧的偏移值,是拖动的建议值
if (child == mMainView){ //主面板拖动范围
if (left < 0){
left = 0;
}else if (left > (mSl_width)){
left = mSl_width;
}
}else if (child == mSlideView){ //左面板拖动范围
if (left > 0){
left = 0;
}else if (left < -mSl_width){
left = mSl_width;
}
}
return left;
}
它默认是返回0的,即拖动不了的,同理它还有纵向的,这里我们不需要,所以不重新,到这里还是拖不动的,因为,我们的ViewDragHelper拚什么拖动呢,我们还要把touch事件传递给他,它才能决定该不该拖动
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mViewDragHelper.shouldInterceptTouchEvent(event); //交给它去判断该不该拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mViewDragHelper.processTouchEvent(event); //有可能会出错,所以try一下
}catch (Exception e){
e.printStackTrace();
}
return true;
}
好了,这里就可以拖动了,但是,只是一个view拖动了,另一个并不跟着动啊,所以我们还要另外重写一个函数,onViewPositionChanged看名字就知道了吧,拖动的view改变的时候调用,所以我们在这里动态的layout就好了
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mMainView){
mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());
}else if (changedView == mSlideView){
mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
}
invalidate();
}
还有,当松开手的时候,我们应该看看左面板滑出来多少了,然后根据值判断是不是该关闭,即我们重写onViewReleased这个方法
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mMainView.getLeft() < mSl_width/2){
closeMenu();
}else {
openMenu();
}
}
@Override
public void computeScroll() { //不停计算,不停调用
if (mViewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this); //不停刷新直到停止滑动
}
}
public void closeMenu() {
mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
ViewCompat.postInvalidateOnAnimation(this); //兼容,刷新界面.
mMenuIsOpen = false;
}
public void openMenu() {
mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
ViewCompat.postInvalidateOnAnimation(this);
mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
mMenuIsOpen = true;
}
在这里介绍下smoothSlideViewTo(View child, int finalLeft, int finalTop)看参数就明白了是吧,但是实现它的效果我们要ViewCompat.postInvalidateOnAnimation(this)刷新界面,这是兼容国产各大rom的,还要重写computeScroll()这个方法,continueSettling(true)返回true表示还没滑玩,所以继续刷新界面.好了到这里我们就做的差不多了,来看看效果吧
好吧,为什么会生成倒的gif图片我也不知道,因为总是大了,弄了好几次,把握不到度啊,治疗治疗颈椎病吧.看到这应该发现问题了吧,当在ViewGroup和listView上左右滑时划不动.看到这也许有人说了,去ononInterceptTouchEvent那设置滑动判断然后返回true拦截事件,再去Listview 的onTouchEvent判断返回false表示我不消费这个事件,我想说我都做了,而且log显示进入了我的判断拦截事件.还是不行,我都开始怀疑人生.怀疑我对touch事件的认知,就去网上看别人怎么说,说的都是我认知的,都不能解决这个问题.真的而且36度的天啊,很热,很沮丧的感觉.反正弄了将近一天,真的各种自己重写view拦截…全部没用…所以没原理没方法真的基本做不出来..突然重写了ViewDragHelper.callback的一个方法就好了,是的,就是这个方法,我真的无法解释,我都无语了.因为我想我事件都交给ViewDragHelper处理了,点ViewDragHelper.shouldInterceptTouchEvent(event)进去看源码,看不懂…看到一个变量名有drag什么,所以试一试的心态重写了这个方法的.真的不知道这是撒原理,平常我们不重写这个不是也能拖动么,所以很少重写,以后我每次都重写了
@Override
public int getViewHorizontalDragRange(View child) {
// 返回拖拽的范围
return mSl_width/3 * 2;
}
重写后效果就能拖了,各控件的touch事件也正常,到这基本完了,最后来弄我们的拖动监听吧
public interface OnSlideListener{
/**
* @param view //触摸的是主界面还是菜单界面
* @param left //滑动了多长,负数向左滑,正数向右
* @param persent //滑动相对于总长度的百分比
*/
void onSlide(View view,int left,float persent);
}
private OnSlideListener mOnSlideListener;
public void setOnSlideListener(OnSlideListener listener){
mOnSlideListener = listener;
}
在 onViewPositionChanged中添加
if (mOnSlideListener != null){
mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
}
嗯,这里还是贴下完整代码吧,等下资源审核通过后我把项目文件下载路径放在最下面,感兴趣的朋友可以去下载看看
import android.content.Context;
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 Root on 2016/6/20.
*/
public class SlideLayout extends FrameLayout{
private View mSlideView;
private View mMainView;
private ViewDragHelper mViewDragHelper;
private int mWd_width;
private int mWd_height;
private int mSl_width;
private boolean mMenuIsOpen;
public SlideLayout(Context context) {
this(context,null);
}
public SlideLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this,1.0f,new MyDragCallBack());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSlideView = getChildAt(0);
mMainView = getChildAt(1);
mWd_width = MeasureSpec.getSize(widthMeasureSpec); //使用测量说明书得到我们想要知道的宽度和高度值
mWd_height = MeasureSpec.getSize(heightMeasureSpec);
int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作测量说明书规定值
int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
mSlideView.measure(sl_widthSpec,sl_heightSpec);
mMainView.measure(widthMeasureSpec,heightMeasureSpec); //主面板和自己一样,都是填充整个窗体,所以测量说明书的规定值一样
setMeasuredDimension(mWd_width, mWd_height); //测量自己用这个方法.
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mSl_width = mSlideView.getWidth();
mSlideView.layout(mSlideView.getLeft() - mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mViewDragHelper.shouldInterceptTouchEvent(event); //交给它去判断该不该拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mViewDragHelper.processTouchEvent(event); //有可能会出错,所以try一下
}catch (Exception e){
e.printStackTrace();
}
return true;
}
class MyDragCallBack extends ViewDragHelper.Callback {
@Override //表示要捕获那个孩子,即处理哪个孩子的拖到事件,返回false不处理
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override //
public int clampViewPositionHorizontal(View child, int left, int dx) { //left相对于屏幕左侧的偏移值,是拖动的建议值
if (child == mMainView){ //主面板拖动范围
if (left < 0){
left = 0;
}else if (left > (mSl_width)){
left = mSl_width;
}
}else if (child == mSlideView){ //左面板拖动范围
if (left > 0){
left = 0;
}else if (left < -mSl_width){
left = mSl_width;
}
}
return left;
}
@Override
public int getViewHorizontalDragRange(View child) {
// 返回拖拽的范围
return mSl_width/3 * 2;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mMainView){
mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());
}else if (changedView == mSlideView){
mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
}
if (mOnSlideListener != null){
mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
}
invalidate();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mMainView.getLeft() < mSl_width/2){
closeMenu();
}else {
openMenu();
}
}
}
@Override
public void computeScroll() { //不停计算,不停调用
if (mViewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this); //不停刷新直到停止滑动
}
}
public void closeMenu() {
mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
ViewCompat.postInvalidateOnAnimation(this); //兼容,刷新界面.
mMenuIsOpen = false;
}
public void openMenu() {
mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
ViewCompat.postInvalidateOnAnimation(this);
mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
mMenuIsOpen = true;
}
public boolean isMenuIsOpen(){
return mMenuIsOpen;
}
public interface OnSlideListener{
/**
* @param view //触摸的是主界面还是菜单界面
* @param left //滑动了多长,负数向左滑,正数向右
* @param persent //滑动相对于总长度的百分比
*/
void onSlide(View view,int left,float persent);
}
private OnSlideListener mOnSlideListener;
public void setOnSlideListener(OnSlideListener listener){
mOnSlideListener = listener;
}
}
完整代码下载路径http://download.csdn.net/detail/z8z87878/9556794
以上是关于鹅厂系列一 : 仿QQ侧滑菜单的主要内容,如果未能解决你的问题,请参考以下文章
Android使用DrawerLayout仿qq6.6版本侧滑效果