您的位置:首页 > 其它

鹅厂系列一 : 仿QQ侧滑菜单

2016-06-22 00:44 134 查看
——不会的东西你不尝试的去做,你永远都不会做

好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: