事件处理——仿酷狗音乐和QQ6.0侧滑菜单

上两篇播客说了下View和ViewGroup的事件处理,这里实现下酷狗音乐和QQ6.0侧滑菜单的效果,先看下酷狗音乐侧滑菜单的效果;这里写图片描述

侧滑菜单其实系统已经提供了DrawerLayout,这里没有用系统的DrawerLayout来实现的,而是自定义View继承自horizontalscrollview来实现的;

1自定义view继承自horizontalscrollview并计算菜单的宽度

public class KGSlidingMenu extends HorizontalScrollView {
    private int mMenuWidth;

    public KGSlidingMenu(Context context) {
        this(context, null);
    }
    public KGSlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.KGSlidingMenu);
        //距离右边的距离
        float rightMargin = array.getDimension(R.styleable.KGSlidingMenu_menuRightMargin, dip2px(50));
        //计算菜单的宽度
        mMenuWidth = (int) (getScreenWidth(context) - rightMargin);
        array.recycle();
    }
}

这里采用的是两个布局,左边是菜单的布局,右边是内容的布局;

2测量菜单和内容的宽高,并重新设置宽高
在onFinishInflate()方法里面进行宽高的测量和设置,

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //布局解析完毕会回调  也就是xml文件布局解析完毕  在onCreate方法中调用
        //指定宽高
        ViewGroup container = (ViewGroup) getChildAt(0);
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置两个子view!");
        }
        //菜单页的宽度是屏幕的宽度 - 右边的一小部分距离(自定义属性)
        menuView = container.getChildAt(0);
        ViewGroup.LayoutParams menuParams = menuView.getLayoutParams();
        menuParams.width = mMenuWidth;
        menuView.setLayoutParams(menuParams);
        //内容页的宽度是屏幕的宽度
        contentView = container.getChildAt(1);
        ViewGroup.LayoutParams contentParams = contentView.getLayoutParams();
        contentParams.width = getScreenWidth(getContext());
        contentView.setLayoutParams(contentParams);
    }

onFinishInflate()方法是在布局解析完毕,也就是xml文件布局解析完毕会调用,在http://www.jianshu.com/p/39ca98752bfdactivtiy中setContentView布局加载的播客里面说了布局的加载,不管是setContentView还是LayoutInflater都会走到下面这个方法,

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

拿到XmlResourceParser这个解析器继续往下走,

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            try {
                ...
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    ...
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    ...
                }
            } catch (XmlPullParserException e) {
                ...
            } catch (Exception e) {
                ...
            } finally {
                ...
            }
            return result;
        }
    }

就会调用rInflate()和rInflateChildren(),rInflateChildren()方法后面还是调用的是rInflate(),

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        ...
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            ...
        }
        //如果finishInflate为true,也就是加载解析完布局就会调用onFinishInflate
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

所以在inflate方法中的rInflate();是不会调用onFinishInflate()方法的,只有 rInflateChildren();才会调用onFinishInflate()方法

if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //这里finishInflate传递的是false不会调用onFinishInflate方法
                    rInflate(parser, root, inflaterContext, attrs, false);
                } 
// Inflate all children under temp against its context.
//这里才会调用onFinishInflate方法,
 rInflateChildren(parser, temp, attrs, true);

这样初步的效果就实现了,但是一进入界面是打开的,所以就要让它每次进入界面时都移动到菜单的宽度,即关闭状态;

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //初始化进入的时候是关闭的,移动一段距离就可以了  在onResume之后调用
        scrollTo(mMenuWidth, 0);
    }

3处理DOWN,UP,MOVE等状态下的滑动
接下来就要在onTouchEvent方法中对DOWN,UP,MOVE这几种状态进行处理,要获取到手指滑动时X的的距离,如果X大于菜单宽度的一半在UP状态下就要自动关闭菜单,反之则打开;

   @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //如果有拦截就不执行自己的onTouchEvent
        if (mIsIntercept) {
            return true;
        }
        //快速滑动触发了下面的就不要执行了
        if (mGestureDetector.onTouchEvent(ev)) {
            return true;
        }
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            //  只需要处理手指抬起  根据当前滚动的距离来判断
            int currentScrollX = getScrollX();
            if (currentScrollX > mMenuWidth / 2) {
                //关闭
                closeMenu();
            } else {
                //打开
                openMenu();
            }
            //确保super.onTouchEvent(ev)不执行
            return true;
        }
        return super.onTouchEvent(ev);
    }

4处理滑动过程中右边的缩放,左边的缩放和透明度
onScrollChanged()方法会在滑动过程中时时监听一个滑动的状态,

   @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //计算一个梯度值  1f - 0f
        float scale = 1f * l / mMenuWidth;
        //右边的缩放最小  0.7f  最大是1f  默认是以中心点缩放
        float rightScale = 0.7f + 0.3f * scale;

        //设置缩放的中心点位置
        ViewCompat.setPivotX(contentView, 0);
        ViewCompat.setPivotY(contentView, contentView.getMeasuredHeight() / 2);
        //设置右边的缩放
        ViewCompat.setScaleX(contentView, rightScale);
        ViewCompat.setScaleY(contentView, rightScale);

        //设置菜单缩放和透明度
        //透明度是由半透明到全部透明  0.7 - 1.0f
        float leftAlpha = 0.5f + (1 - scale) * 0.5f;
        ViewCompat.setAlpha(menuView, leftAlpha);
        //缩放  0.7f - 1.0f
        float leftScale = 0.7f + (1 - scale) * 0.3f;
        ViewCompat.setScaleX(menuView, leftScale);
        ViewCompat.setScaleY(menuView, leftScale);
        //设置文字平移
        ViewCompat.setTranslationX(menuView, l * 0.26f);
    }

ViewCompat是android官方实现兼容的一个帮助类,可以设置一些动画,缩放等效果;

5处理手势快速滑动
在处理手势快速滑动方法,系统提供了一个GestureDetector类,它底层已经帮我们做了很好的处理,使用的时候直接new一个就可以了,

GestureDetector mGestureDetector = new GestureDetector(context, mGestureListener);

需要传一个上下文和OnGestureListener,在new一个OnGestureListener的时候发现需要实现很多我们不需要的方法,但是不实现就会报错,可以选择SimpleOnGestureListener,SimpleOnGestureListener继承自OnGestureListener,点进去一看什么都没有做,但是可以根据自己的需要去实现方法;

private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            //快速滑动  只要快速滑动就会有回调  快速往左边滑动的时候是一个负数,往右边滑动是一个正数
            if (mMenuIsOpen) {
                //打开往右边快速滑动就去切换(关闭)
                if (velocityX < 0) {
                    closeMenu();
                    return true;
                }
            } else {
                //关闭的时候往左边快速滑动切换(打开)
                if (velocityX > 0) {
                    openMenu();
                    return true;
                }
            }
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    };

这里是左右滑动,所以只需要根据velocityX的值去判断;

6处理事件拦截
在菜单栏打开状态下,点击右边的内容区域要将菜单栏关闭,同时右边的内容区域不能有相应的点击效果,在菜单栏关闭状态下,点击右边内容区域才会有相应的点击效果,这样子就需要在onInterceptTouchEvent()方法中进行事件拦截的处理;

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        mIsIntercept = false;
        //当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件,
        if (mMenuIsOpen) {
            float currentX = ev.getX();
            if (currentX > mMenuWidth) {
                //关闭菜单
                closeMenu();
                //子view不需要相应任何的事件  拦截子view的事件
                mIsIntercept = true;
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

这样就大致实现了上面的效果,不过右边内容区域用的是TextView,如果右边用的是ListView或者recyclerview或者ViewPager等,还要对事件分发及事件拦截等进行相应处理才可以达到想要的效果;

QQ6.0侧滑菜单效果就是在酷狗音乐侧滑菜单栏效果的基础进行了些修改,下面是QQ6.0侧滑菜单的效果;
这里写图片描述

和酷狗音乐侧滑菜单栏效果相比,左右滑动的时候没有缩放效果,但是在左右滑动的时候右边的内容区域会根据滑动的距离会有透明度的变化;实现透明度的变化这里有两种思路;

1、在布局文件中放一个大的背景,在滑动的的时候做一个回调,根据回调获取的值设置背景的透明度
2、在测量的时候先将布局类容移除调,创建一个RelativeLayout将内容布局添加到RelativeLayout,接着创建一个透明度的View,将View也添加到RelativeLayout中,最后将RelativeLayout添加的之前的内容布局的父布局中,

这里采用的是第二种思路实现的;
在onFinishInflate()方法中进行布局移除,创建,添加的一系列操作;

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //布局解析完毕会回调  也就是xml文件布局解析完毕  在onCreate方法中调用
        //指定宽高
        ViewGroup container = (ViewGroup) getChildAt(0);
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置两个子view!");
        }

        //菜单页的宽度是屏幕的宽度 - 右边的一小部分距离(自定义属性)
        menuView = container.getChildAt(0);
        ViewGroup.LayoutParams menuParams = menuView.getLayoutParams();
        menuParams.width = mMenuWidth;
        menuView.setLayoutParams(menuParams);
        //内容页的宽度是屏幕的宽度
        contentView = container.getChildAt(1);
        ViewGroup.LayoutParams contentParams = contentView.getLayoutParams();
        //移除之前的布局
        container.removeView(contentView);
        //然后在外面套一层阴影
        RelativeLayout contentContainer=new RelativeLayout(getContext());
        contentContainer.addView(contentView);
        //创建阴影
        mShadowView=new View(getContext());
        mShadowView.setBackgroundColor(Color.parseColor("#55000000"));
        contentContainer.addView(mShadowView);
        //最后放回原来的位置
        contentParams.width = getScreenWidth(getContext());
        contentContainer.setLayoutParams(contentParams);

        container.addView(contentContainer);
        //设置为透明
        mShadowView.setAlpha(0.0f);
    }

添加进去后,在滑动的时候进行监听不断改变内容区域的透明度;

   @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
//        //计算一个梯度值  1f - 0f
        float scale = 1f * l / mMenuWidth;
        //控制阴影  0--1
        float alphaScale=1-scale;
        mShadowView.setAlpha(alphaScale);
        //设置文字平移
        ViewCompat.setTranslationX(menuView, l * 0.6f);
    }

这样效果就实现了,如上面写的有不对的地方,欢迎交流;

仿酷狗音乐侧滑菜单源码:
http://download.csdn.net/detail/wangwo1991/9896972

仿QQ6.0侧滑菜单源码:
http://download.csdn.net/detail/wangwo1991/9899845

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redrain仿酷狗音乐播放器     这篇文章只是对开源的说明!关于这个Redrain音乐盒的发布程序的说明和使用方法,见《Redrain仿酷狗音乐播放器开发完毕,发布测试程序》。    今天,我把这个项目的源代码上传。包括了可以编译工程所需的所有代码文件,已经软件的布局文件,但是没有包括软件需要的素材,这也是为了避免引起与酷狗播放器的版权问题。        我在这个项目中使用的是 UiLib 库而不是 DuiLib 库,UiLib库是DuiLib库的扩展版本,增加了一些动画控件的支持,扩展了部分控件,但是核心代码并没有改变,与DuiLib使用方法完全一样,也可以用UiLib库直接编译使用 DuiLib 库编写的代码。项目中的 UiLib 是我为了适应仿酷狗而专门修改过的,也修复过必要的 bug,所以如果你使用原版的 DuiLib 或者 UiLib 库去编译这个功能,最终的程序效果和我发布的不一样。关于 bug的修复请看博客中更早期的文章。我自己使用并且维护的DuiLib库和UiLib库的下载地址见博客:《Redrain个人维护并使用的DuiLib和UiLib库源码下载地址》       在这个项目源码中,同时包含了webkit内核浏览器控件、音乐播放类、换肤功能、拖拽功能、菜单等等。这个菜单的功能如下:         1、可以展现多级菜单         2、可内嵌自定义控件,并且控件可以向主窗体发送消息,如图的红色叹号就是个按钮控件,可以制作酷狗音乐的托盘菜单的播放暂停按钮和进度控制进度条。         3、菜单拥有阴影效果         4、菜单可以自定义前方显示小图标,并且可以控制图标的大小和是否显示         5、菜单可以根据是否拥有子菜单决定是否显示小箭头         6、菜单可以添加分割线         7、每个菜单项都可以单选和复选的功能         8、优化菜单的xml描述文件,编写方便容易,如果要写一个二级菜单,比如编写图片中的菜单测试4以及他的子菜单,只需如下代码就可以了         9、可以通过键盘的按钮控制菜单的选项         10、每个菜单项的高度宽度是任意调整的 在这个项目中,还有一些未实现的功能,但是我并不打算继续完成这些功能了:      1、退出程序时逐渐缩小的动画特效      2、各个菜单的响应      3、歌词功能(已经有网友做出来了,我就不另外做了)      4、嵌入桌面的桌面歌词 注意 此项目开源代码只是为了学习交流,不可用于商业程序,源码对使用者造成的损失,概不负责! 源码下载地址:点击打开链接 via http://blog.csdn.net/zhuhongshu/article/details/41037875

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值