自定义View ----QQ5.0左边侧滑 + 动画

  1. xml

    activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <com.guyulei.myqq50.DragLayout
        android:background="@mipmap/bk"
        android:id="@+id/dl_dragLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:orientation="vertical"
            android:background="#f00"
            android:layout_width="200dp"
            android:layout_height="match_parent">
            <RelativeLayout
                android:background="#0ff"
                android:layout_width="match_parent"
                android:layout_height="150dp">
                <ImageView
                    android:layout_marginLeft="15dp"
                    android:layout_centerVertical="true"
                    android:src="@mipmap/qq4"
                    android:layout_width="135dp"
                    android:layout_height="135dp"/>
            </RelativeLayout>

            <ListView
                android:id="@+id/left_listView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </ListView>

        </LinearLayout>

        <LinearLayout
            android:orientation="vertical"
            android:background="#0f0"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <RelativeLayout
                android:background="#f0f"
                android:layout_width="match_parent"
                android:layout_height="88dp">
                <ImageView
                    android:id="@+id/iv_head"
                    android:layout_marginLeft="15dp"
                    android:layout_centerVertical="true"
                    android:src="@mipmap/qq4"
                    android:layout_width="77dp"
                    android:layout_height="77dp"/>
            </RelativeLayout>

            <ListView
                android:id="@+id/main_listView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </ListView>

        </LinearLayout>

    </com.guyulei.myqq50.DragLayout>

</LinearLayout>

2.自定义view

public class DragLayout extends FrameLayout

package com.guyulei.myqq50;

import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
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;
import android.widget.LinearLayout;

/**
 * Created by Administrator on 2016/6/30 0030.
 * 1、为什么不继承 ViewGroup,因为继承 ViewGroup 需要重写 onMeasure()和实现 onLayout()方法,自己
 * 实现子 view 的测量和摆放,在这里我们不需要自己去做测量和摆放,而 FrameLayout 已经对这两个方法进
 * 行了具体实现,所以继承 FrameLayout 更加简单省事
 * 2、为什么不继承 RelativeLayout,因为这里我们只需要层级关系,不需要相对关系,继承 RelativeLayout
 * 界面效果是一样的,但 RelativeLayout 对 FrameLayout 多了相对关系的计算,效率会低一些,所以选择继
 * 承 FrameLayout
 */
public class DragLayout extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private LinearLayout   mLeftContent;
    private LinearLayout   mMainContent;
    private int            mWidth;
    private int            mHeight;
    private int            mRange;
    private int mLeftWidth;

    //状态值
    public enum Status {
        Open,
        Close,
        Draging;
    }
    //默认状态值
    private Status status = Status.Close;
    //监听对象声明
    private OnUpdataDragStateListener mOnUpdataDragStateListener;
    //对外接口
    public interface OnUpdataDragStateListener {
        void onOpen();
        void onClose();
        void onDragging(float percent);
    }

    //对外接口回调方法
    public void setOnUpdataDragStateListener(OnUpdataDragStateListener onUpdataDragStateListener) {
        this.mOnUpdataDragStateListener = onUpdataDragStateListener;
    }
    //串联构造方法

    /**
     * 我们可以通过串连三个构造方法的方式实现只调用一次 init()方法
     * 这样无论是代码创建还是布局在 xml 中都能调用到我们的初始化代码
     *
     * @param context
     */
    //代码创建时调用
    public DragLayout(Context context) {
        this(context, null);
    }

    //布局在 xml 中,实例化时调用
    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化
        //1.辅助类ViewDragHelper  forparent 2个拖拽控件的父控件 ,sensitivity子控件拖拽的敏感度
        //callback:事件回调
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            // tryCaptureView俘获; 夺取; 夺得(尝试夺取子控件)
            //返回值代表 子控件 是否可以被夺取
            public boolean tryCaptureView(View child, int pointerId) {
                return true;
            }

            //可通过计算测量水平或者垂直方向拖拽的范围  返回>0即可
            @Override
            public int getViewHorizontalDragRange(View child) {
                return mRange;
            }

            @Override
            //clamp紧紧抓住; 紧夹住(紧抓着俘获的view(child)水平或者垂直移动位置)
            //dx 变化量(移动的差值 newleft-oldleft)
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                //dx累加  是  left
                if (child == mMainContent) {
                    //拖拽为主面板时
                    left = fixleft(left);
                }
                return left;
            }

            @Override
            //松手时  回调(Released释放)
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                // xvel水平方向上的速度 左为-    右为+
                if (xvel == 0 && mMainContent.getLeft() > mRange / 2) {
                    open();
                } else if (xvel > 0) {
                    //有向右的速度
                    open();
                } else {
                    close();
                }
            }

            //位置发生移动  调用
            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                if (changedView == mLeftContent) {
                    //控制底下的子控件不能移动(左面板)
                    mLeftWidth = mLeftContent.getMeasuredWidth();
                    mLeftContent.layout(0, 0, mLeftWidth, mHeight);
                    //左面板移动的距离 累加给主面板(移动左面板)
                    int oldLeft = mMainContent.getLeft();
                    int newLeft = oldLeft + dx;
                    //修正左边的位置
                    newLeft = fixleft(newLeft);
                    mMainContent.layout(newLeft, 0, newLeft + mWidth, mHeight);
                }
                // dispatch派遣,调度; (迅速地)发出; 迅速处理
                dispatchEvent();
                invalidate();
            }
        });
    }

    //2  mViewDragHelper抢过触摸事件自己决定应该怎么处理
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    //3.mViewDragHelper抢过触摸事件后,进行处理,返回值true
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    //4.onFinishInflate()在控件 inflate 完成时会被调用查找子控件
    //可以通过 findViewById()的方式查找子控件  getChildAt(0)
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mLeftContent = (LinearLayout) getChildAt(0);
        mMainContent = (LinearLayout) getChildAt(1);
    }

    // 5.onSizeChanged()调用的次数比 onMeasure()少,在这里我们在 onSizeChanged()方法中去获取宽高
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取整个控件的宽度和高度
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //主界面拖拽的范围(自定义)(0-mRange)(0 < left <mRange)
        mRange = (int) (mWidth * 0.6f);
    }

    //6.修正左边的距离
    private int fixleft(int left) {
        if (left < 0) {
            left = 0;
        } else if (left > mRange) {
            left = mRange;
        }
        return left;
    }

    //7.打开 smooth变平和,变缓和;Slide Mountain 斯来得山(凯次克来最高峰)
    //View child, int finalLeft, int finalTop
    private void open() {
        open(true);
    }

    private void open(boolean isSmooth) {
        int finalLeft = mRange;
        if (isSmooth) {
            //开启平滑
            if (mViewDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                //重绘界面 Compat兼容性;  postInvalidateOnAnimation 动画结束之后再重绘 防止丢帧
                ViewCompat.postInvalidateOnAnimation(this);
            } else {
                mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight);
            }
        }
    }

    //8. 关闭
    private void close() {
        close(true);
    }

    private void close(boolean isSmooth) {
        int finalLeft = 0;
        if (isSmooth) {
            //开启平滑
            if (mViewDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                //重绘界面
                //invalidate();
                ViewCompat.postInvalidateOnAnimation(this);
            } else {
                mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight);
            }
        }
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        //维持动画
        if (mViewDragHelper.continueSettling(true)) {
            //重绘
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    //9.加动画  回调
    private void dispatchEvent() {
        //获取 百分百 0.0f - 1.0f
        float percent = mMainContent.getLeft() * 1.0f / mRange;

        Status lastStatus = status;
        //状态更新
        if (percent == 0) {
            status = Status.Close;
        } else if (percent == 1) {
            status = Status.Open;
        } else {
            status = Status.Draging;
        }
        //接口回调
        if (mOnUpdataDragStateListener != null) {
            mOnUpdataDragStateListener.onDragging(percent);
        }
        //
        if (lastStatus != status && mOnUpdataDragStateListener != null) {
            if (status == Status.Open) {
                mOnUpdataDragStateListener.onOpen();
            } else if (status == Status.Close) {
                mOnUpdataDragStateListener.onClose();
            }
        }

        //左面板 缩放  位移  透明的
        //0.0f-1.0f   ---0.5f-1.0f
        //0.5f + percent * (0.5f);
        mLeftContent.setScaleX(0.5f + percent * (0.5f));
        mLeftContent.setScaleY(0.5f + percent * (0.5f));
        //位移 evaluate(percent,-mw,0)
        mLeftContent.setTranslationX(evaluate(percent, -mWidth * 0.5f, 0f));
        //透明的evaluate(percent,0.2f,1.0f)
        mLeftContent.setAlpha(evaluate(percent, 0.2f, 1.0f));

        //主面板 缩放evaluate(percent,0.2f,1.0f)
        mMainContent.setScaleY(evaluate(percent, 1.0f, 0.8f));
        //背景亮度   滤波器Filter  估价,估值evaluate  门童; 搬运工人Porter欺骗; 把…改头换面;Duff
        getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);

    }

    public Object evaluateColor(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 (int) ((startA + (int) (fraction * (endA - startA))) << 24) |
                (int) ((startR + (int) (fraction * (endR - startR))) << 16) |
                (int) ((startG + (int) (fraction * (endG - startG))) << 8) |
                (int) ((startB + (int) (fraction * (endB - startB))));
    }

    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

3.MainActivity

public class MainActivity extends Activity

package com.guyulei.myqq50;

import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.CycleInterpolator;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.util.Random;

import utils.MyToast;

public class MainActivity extends Activity {

    private DragLayout mDragLayout;
    private ListView   mMainlistView;
    private ListView   mLeftlistView;
    private ImageView  mIvhead;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //主页面imag对象
        mIvhead = (ImageView) findViewById(R.id.iv_head);
        mDragLayout = (DragLayout) findViewById(R.id.dl_dragLayout);
        mDragLayout.setOnUpdataDragStateListener(new DragLayout.OnUpdataDragStateListener() {
            @Override
            public void onOpen() {
                MyToast.showToast(getApplicationContext(), "onOpen");
                ////面板打开时,左面板上的 listview 随机滑动到 0 到 50 间的某个位置
                mLeftlistView.smoothScrollToPosition(new Random().nextInt(20));
            }

            @Override
            public void onClose() {
                MyToast.showToast(getApplicationContext(), "onClose");
                ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 10f);
                valueAnimator.setDuration(1000);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (float) animation.getAnimatedValue();
                        mIvhead.setTranslationX(value);
                    }
                });
                //动画插值器
                valueAnimator.setInterpolator(new CycleInterpolator(3));
                valueAnimator.start();
            }

            @Override
            public void onDragging(float percent) {
                MyToast.showToast(getApplicationContext(), "onDragging" + percent);
                //percent 0.0f -> 1.0f  => 1.0f -> 0.0f   ==> 1-percent
                mIvhead.setAlpha(1-percent);
            }
        });

        //
        mMainlistView = (ListView) findViewById(R.id.main_listView);
        //main数据
        String[] str_main = new String[]{"guyulei", "guyulei1", "guyulei2", "guyulei3", "guyulei4"
                , "guyulei5", "guyulei6", "guyulei7", "guyulei8", "guyulei9", "guyulei10",
                "guyulei", "guyulei1", "guyulei2", "guyulei3", "guyulei4"
                , "guyulei5", "guyulei6", "guyulei7", "guyulei8", "guyulei9", "guyulei10",
                "guyulei", "guyulei1", "guyulei2", "guyulei3", "guyulei4"
                , "guyulei5", "guyulei6", "guyulei7", "guyulei8", "guyulei9", "guyulei10"};

        //设置适配器:ArrayAdapter
        mMainlistView.setAdapter(new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, str_main) {
            @Override
            //重新getView  改变字体颜色
            public View getView(int position, View convertView, ViewGroup parent) {

                TextView view = (TextView) super.getView(position, convertView, parent);
                view.setTextColor(Color.BLUE);
                return view;
            }
        });

        //
        mLeftlistView = (ListView) findViewById(R.id.left_listView);
        String[] str_left = new String[]{"顾雨磊", "顾雨磊1", "顾雨磊2", "顾雨磊3", "顾雨磊4"
                , "顾雨磊5", "顾雨磊6", "顾雨磊7", "顾雨磊8", "顾雨磊9", "顾雨磊10", "顾雨磊11"
                , "顾雨磊12", "顾雨磊13", "顾雨磊14", "顾雨磊15", "顾雨磊16", "顾雨磊17", "顾雨磊18"
                , "顾雨磊19"};
        mLeftlistView.setAdapter(new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, str_left) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                TextView view = (TextView) super.getView(position, convertView, parent);
                view.setTextColor(Color.BLACK);
                return view;
            }
        });

    }
}
时间: 2024-10-21 22:46:19

自定义View ----QQ5.0左边侧滑 + 动画的相关文章

Android自定义View之仿QQ侧滑菜单实现

最近,由于正在做的一个应用中要用到侧滑菜单,所以通过查资料看视频,学习了一下自定义View,实现一个类似于QQ的侧滑菜单,顺便还将其封装为自定义组件,可以实现类似QQ的侧滑菜单和抽屉式侧滑菜单两种菜单. 下面先放上效果图: 我们这里的侧滑菜单主要是利用HorizontalScrollView来实现的,基本的思路是,一个布局中左边是菜单布局,右边是内容布局,默认情况下,菜单布局隐藏,内容布局显示,当我们向右侧滑,就会将菜单拉出来,而将内容布局的一部分隐藏,如下图所示: 下面我们就一步步开始实现一个

android自定义View之(四)------一键清除动画

1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解,做了一个一键清除的动画.当年,我实现这个是用了几张图片,采用Frame anination的方式来实现,但是这个方法,不灵活,并且占资源,下面,我就采用自定义view的方法来实现这个功能. 2.效果图: 3.具体详细代码 3.1 \res\values\attrs_on_key_clear_circle_view.xml <resources> <de

Android自定义view教程01-------------Android的Frame动画详解

本系列博文 最终的目的是能教会大家自己实现比较复杂的android 自定义控件.所以知识点不仅仅局促在自定义view本身上面.实际上现在github上一些做的比较出色的自定义控件 大部分都是由三个部分组成 第一:动画 第二:自定义view 第三:触摸滑动控制.所以我们这个系列也是由动画作为开篇.最终会带着大家分析几个github上比较出色的自定义控件. Android 的frame动画是比较简单基础的内容,在以往的2.x 3.x版本很多人都会去使用这个 来作为loading 图的实现方法.但是最

Android自定义view教程04-------------自定义属性动画

不太会美工,所以随便写了个菜单打开关闭的动画,主要是教会大家如何使用属性动画,可以这么说 学会属性动画 前面的fream动画和tween动画可以不用看了,因为他们2能做的,属性动画也能做, 他们2不能做的,属性动画也能做. 直接上代码吧,注释写的还算详细. 主activity代码 实际上没啥好看的,主要就是使用了dialogfragment,没有用dialog,因为谷歌后来推荐 我们使用这个dialogfragment,而且这个确实比dialog要优秀方便很多. package com.exam

Android自定义View绘图实现拖影动画

前几天在"Android绘图之渐隐动画"一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下.这次效果好多了. 先看效果吧: 然后我们来说说基本的做法: 根据画笔宽度,计算每一条线段两个顶点对应的四个点,四点连线,包围线段,形成一个路径. 后一条线段的路径的前两个点,取(等于)前一条线段的后两点,这样就衔接起来了. 把Path的Style修改为FILL,效果是这样的: 可以看到一个个四边形,连成了路径. 好啦,现在说说怎样根据两点计算出包围

ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj623565791/article/details/39257409   就是利用HorizontalScrollView实现的,简单实用; 如http://blog.csdn.net/manoel/article/details/39013095/   通过改造SlidingMenu实现,没有改变原有SlidingMenu功能,屏幕边缘侧滑也可以.... 相对来说ViewDragHelper实现方式最为复杂

(转)ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

最近在做项目,涉及到类似QQ的页面的滑动.但是却遇到了侧滑和ViewPager冲突的问题,头疼了很长时间,最后在网上发现了这篇博客,转载过来供自己学习参考(写这篇博客的原创作者,因为我发现这篇博客的地方也是别人转载的,所以具体是有哪位大牛完成的我也不得而知.转载文章不能标出原创作者和出处,请见谅) QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj623565791/article/details/39257409 就是利用HorizontalScrollV

【案例分享】仿QQ5.0侧滑菜单ResideMenu

本文由孙国威 原创.如需转载,请注明出处! 为了后续对这个项目进行优化,比如透明度动画.背景图的位移动画,以及性能上的优化. 我把这个项目上传到github上面,请大家随时关注. github地址https://github.com/sunguowei 最近项目要做一个QQ5.0的侧滑菜单效果,和传统的侧滑菜单存在着一些差异.想必大家都已经见识过了. 为了不重复发明轮子,先去github上面搜索了一番. 发现了几个类似的,但是还是有一些不同. 下面是搜索到的类似的开源项目. RESideMenu

【转】仿QQ5.0侧滑菜单ResideMenu

本文由孙国威 原创.如需转载,请注明出处! 原文:http://blog.csdn.net/manoel/article/details/39013095 为了后续对这个项目进行优化,比如透明度动画.背景图的位移动画,以及性能上的优化. 我把这个项目上传到github上面,请大家随时关注. github地址https://github.com/sunguowei 最近项目要做一个QQ5.0的侧滑菜单效果,和传统的侧滑菜单存在着一些差异.想必大家都已经见识过了. 为了不重复发明轮子,先去githu