QQ 5.0的一些特效学习 一

摘要:
尽管QQ5.0已经过去了很长时间,但一些特殊效果仍然值得学习:源代码指向我导入的jar包,其中一个是support.v4包的更高版本,它需要在v4包中包含ViewDragHelper。我在这里使用support-v4:24.1,还添加了一个9年的androids jar包,这是一个开源的动画库,易于使用。˃˃LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android“android:layout_width=”match_parent“android:layout_height=”matche_parent”android:orientation=“vertical”android:paddingLeft=“20dp”android:ppaddingTop=“50dp”˃预览最后一个activity_main.xml在此处设置背景图像˂!

虽然QQ5.0已经过去很久了,但是有些特效还是值得学习的

 效果:

QQ 5.0的一些特效学习 一第1张

 源码点我

导入的jar包,

一个是高版本的support.v4包,需要这个v4包中有ViewDragHelper.

我这里使用的是support-v4:24.1.1

还要添加一个nineoldandroids的jar包,这是一个开源的动画库,使用方便。

GitHub地址:https://github.com/JakeWharton/NineOldAndroids

 项目源码里也有这些

布局主要分为菜单界面和主界面

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/my_layout"
    android:background="#ffffff"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#18B4ED" >

        <ImageView
            android:layout_width="30dp"
            android:layout_marginLeft="15dp"
            android:id="@+id/iv_head"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:background="@drawable/head" />
    </RelativeLayout>
    
    <ListView android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/main_listview"
        android:listSelector="@android:color/transparent"></ListView>

</LinearLayout>

预览效果:

QQ 5.0的一些特效学习 一第2张

layout/layout_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="20dp"
    android:paddingTop="50dp"
   >

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/head" />

    <ListView
        android:id="@+id/menu_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:listSelector="@android:color/transparent" >
    </ListView>

</LinearLayout>


预览

QQ 5.0的一些特效学习 一第3张

最后activity_main.xml

这里设置背景图片

<com.example.xw.qqslidemenu.SlideMenu 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:id="@+id/slideMenu"
    android:background="@drawable/bg"
     >

    <!-- 菜单的布局 -->
    <include layout="@layout/layout_menu"/>
    <!-- 主界面的布局 -->
    <include layout="@layout/layout_main"/>

</com.example.xw.qqslidemenu.SlideMenu>

接下来开始写我们的自定义SlideMenu

1.继承FrameLayout,这里利用FrameLayout的特性,方便

2.实现Drag拖拉跟随即动画,缩放等

3.onTouchEvent的事件处理

首先上代码,再来分析

import android.content.Context;
import android.support.v4.graphics.ColorUtils;
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 com.nineoldandroids.animation.FloatEvaluator;
import com.nineoldandroids.animation.IntEvaluator;
import com.nineoldandroids.view.ViewHelper;

import java.net.InterfaceAddress;

/**
 * Created by xw on 2016/8/21.
 */
public class SlideMenu extends FrameLayout {

    private View menuView;//菜单的view
    private View mainView;//主界面的view
    private ViewDragHelper viewDragHelper;
    private int width;
    private float dragRange;//拖拽范围
    private FloatEvaluator floatEvaluator;//float的计算器
    private IntEvaluator intEvaluator;//int的计算器
    private OnDragStateChangeListener listener;//回调监听器

    //定义状态常量
    enum DragState{
        Open,Close;
    }
    private DragState currentState = DragState.Close;//当前SlideMenu的状态默认是关闭的

    public SlideMenu(Context context) {
        super(context);
        init();
    }

    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
       viewDragHelper=ViewDragHelper.create(this,callback);
        floatEvaluator = new FloatEvaluator();
        intEvaluator = new IntEvaluator();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return viewDragHelper.shouldInterceptTouchEvent(ev);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

    private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() {
        /**
         *用于判断是否捕获当前child的触摸事件
         * chid:当前触摸的子View
         * return true:捕获并处理 false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==menuView||child==mainView;
        }

        /**
         * 获取view水平方向的拖拽范围,但是目前不能限制边界
         * 返回值目前用在手指抬起的时候view缓慢移动的动画世界的计算上面
         * 最好不能返回0
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return (int) dragRange;
        }

        /**
         * 控制child在水平方向的移动 left:
         * @param child
         * @param left 表示你想让child的left改变的值,left=child.getLeft+dx
         * @param dx 本次移动距离
         * @return 表示你真正想让child的left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
           if(child==mainView){
               if (left<0) left=0; //限制左边
               if(left>dragRange) left= (int) dragRange; //限制右边
           }

            return left;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
           if(changedView==menuView){
               //固定住menuView
               menuView.layout(0,0,menuView.getMeasuredWidth(),menuView.getMeasuredHeight());

               //让mainView移动起来
               int newLeft=mainView.getLeft()+dx;
               if(newLeft<0) newLeft=0;
               if(newLeft>dragRange) newLeft= (int) dragRange;
               mainView.layout(newLeft,mainView.getTop()+dy,newLeft+mainView.getMeasuredWidth(),mainView.getBottom()+dy);
           }

            //1,计算滑动的百分比
            float fraction =mainView.getLeft()/dragRange;
            //2执行伴随动画
            executeAnim(fraction);

            if(fraction==0 && currentState!=DragState.Close){
                //更改状态为关闭,并回调关闭的方法
                currentState = DragState.Close;
                if(listener!=null)listener.onClose();
            }else if (fraction==1f && currentState!=DragState.Open) {
                //更改状态为打开,并回调打开的方法
                currentState = DragState.Open;
                if(listener!=null)listener.onOpen();
            }
            //将drag的fraction暴漏给外界
            if(listener!=null){
                listener.onDraging(fraction);
            }
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if(mainView.getLeft()<dragRange/2){
                close();
            }
            else{
                open();
            }

            //处理用户的稍微滑动
            if(xvel>200 && currentState!=DragState.Open){
                open();
            }else if (xvel<-200 && currentState!=DragState.Close) {
                close();
            }
        }
    };

    private void open() {
        viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange,0);
        ViewCompat.postInvalidateOnAnimation(SlideMenu.this);
    }

    private void close() {
        viewDragHelper.smoothSlideViewTo(mainView,0,0);
        ViewCompat.postInvalidateOnAnimation(SlideMenu.this);
    }

    public void executeAnim(float fraction) {
        //缩小mainView
        ViewHelper.setScaleX(mainView,floatEvaluator.evaluate(fraction,1f,0.8f));
        ViewHelper.setScaleY(mainView,floatEvaluator.evaluate(fraction,1f,0.8f));
        //移动menuView
        ViewHelper.setTranslationX(menuView,intEvaluator.evaluate(fraction,-menuView.getMeasuredWidth()/2,0));

        //放大menuView
        ViewHelper.setScaleX(menuView,floatEvaluator.evaluate(fraction,0.5f,1f));
        ViewHelper.setScaleY(menuView,floatEvaluator.evaluate(fraction,0.5f,1f));
        //改变menuView的透明度

        ViewHelper.setAlpha(menuView,floatEvaluator.evaluate(fraction,0.3f,1f));

    }

    public  void computeScroll(){
        if(viewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(SlideMenu.this);
        }
    }
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(getChildCount()!=2){
            throw new IllegalArgumentException("Slide should have two children");
        }
        menuView=getChildAt(0);
        mainView=getChildAt(1);
    }

    /**
     * 该方法在onMeasure()方法执行完成之后完成,
     * 可以在该方法初始化自己的宽高和子View的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width=getMeasuredWidth();
        dragRange=width*0.6f;
    }

    public void setOnDragStateChangeListener(OnDragStateChangeListener listener){
        this.listener = listener;
    }


    interface OnDragStateChangeListener{
        void onOpen();
        void onClose();
        void onDraging(Float fraction);
    }
}

第一步:

    

   @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(getChildCount()!=2){
            throw new IllegalArgumentException("Slide should have two children");
        }
        menuView=getChildAt(0);
        mainView=getChildAt(1);
    }

    /**
     * 该方法在onMeasure()方法执行完成之后完成,
     * 可以在该方法初始化自己的宽高和子View的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width=getMeasuredWidth();
        dragRange=width*0.6f;
    }

得到两个子View,和得到width和拖动范围dragRange

第二步

       初始化ViewDragHelper

private void init(){
        viewDragHelper = ViewDragHelper.create(this, callback);
        floatEvaluator = new FloatEvaluator();
        intEvaluator = new IntEvaluator();
    }

写ViewDragHelper.Callback callback

按照顺序复写方法:

1.捕获menuView或者mainView

/**
         * 用于判断是否捕获当前child的触摸事件 
         * child: 当前触摸的子View 
         * return: true:就捕获并解析 false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==menuView || child==mainView;
        }

2.设置水平方向移动范围

public int getViewHorizontalDragRange(View child) {
            return (int) dragRange;
        }

3.设置移动方向和限制边界

/**
         * 控制child在水平方向的移动 left:
         * 表示ViewDragHelper认为你想让当前child的left改变的值,left=chile.getLeft()+dx dx:
         * 本次child水平方向移动的距离 return: 表示你真正想让child的left变成的值
         */
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(child==mainView){
                if(left<0)left=0;//限制mainView的左边
                if(left>dragRange)left=(int) dragRange;//限制mainView的右边
            }
            return left;
        }

4,实现伴随移动和根据移动百分比执行动画,关于接口回调方法和执行动画代码参考一开始的全代码

 @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
           if(changedView==menuView){
               //固定住menuView
               menuView.layout(0,0,menuView.getMeasuredWidth(),menuView.getMeasuredHeight());

               //让mainView移动起来
               int newLeft=mainView.getLeft()+dx;
               if(newLeft<0) newLeft=0;
               if(newLeft>dragRange) newLeft= (int) dragRange;
               mainView.layout(newLeft,mainView.getTop()+dy,newLeft+mainView.getMeasuredWidth(),mainView.getBottom()+dy);
           }

            //1,计算滑动的百分比
            float fraction =mainView.getLeft()/dragRange;
            //2执行伴随动画
            executeAnim(fraction);

            if(fraction==0 && currentState!=DragState.Close){
                //更改状态为关闭,并回调关闭的方法
                currentState = DragState.Close;
                if(listener!=null)listener.onClose();
            }else if (fraction==1f && currentState!=DragState.Open) {
                //更改状态为打开,并回调打开的方法
                currentState = DragState.Open;
                if(listener!=null)listener.onOpen();
            }
            //将drag的fraction暴漏给外界
            if(listener!=null){
                listener.onDraging(fraction);
            }
        }

5.释放时根据位置或者滑动加速度控制菜单是打开还是关闭

 @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if(mainView.getLeft()<dragRange/2){
                close();
            }
            else{
                open();
            }

            //处理用户的稍微滑动
            if(xvel>200 && currentState!=DragState.Open){
                open();
            }else if (xvel<-200 && currentState!=DragState.Close) {
                close();
            }
        }

6最后别忘了拦截Touch事件

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return viewDragHelper.shouldInterceptTouchEvent(ev);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

7.

public  void computeScroll(){
        if(viewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(SlideMenu.this);
        }
    }

最后加上回调接口,用枚举表示状态

最后给listview填充数据,及实现imageView的一些特效

MainActivity

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.animation.CycleInterpolator;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;

import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private ListView mainlv;
    private  ListView menulv;
    private SlideMenu slideMenu;
    private ImageView iv_head;

    public static final String[] sCheeseStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
    };
    public static final String[] NAMES = new String[] { "宋江", "卢俊义", "吴用",
            "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
            "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
            "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
            " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
            "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
            "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
            "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
            "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv_head= (ImageView) findViewById(R.id.iv_head);
        mainlv= (ListView) findViewById(R.id.main_listview);
        menulv= (ListView) findViewById(R.id.menu_listview);
        mainlv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,NAMES));

        menulv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,sCheeseStrings){
            @Override
            //改变textView颜色
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView textView = (TextView) super.getView(position, convertView, parent);
                textView.setTextColor(Color.WHITE);
                return textView;
            }
        });

        slideMenu= (SlideMenu) findViewById(R.id.slideMenu);
        slideMenu.setOnDragStateChangeListener(new SlideMenu.OnDragStateChangeListener() {
            @Override
            public void onOpen() {
                menulv.smoothScrollToPosition(new Random().nextInt(menulv.getCount()));
            }

            @Override
            public void onClose() {
                ViewPropertyAnimator.animate(iv_head).translationXBy(15)
                        .setInterpolator(new CycleInterpolator(4))
                        .setDuration(500)
                        .start();
            }

            @Override
            public void onDraging(Float fraction) {
                ViewHelper.setAlpha(iv_head,1-fraction);
            }
        });

    }
}

效果

QQ 5.0的一些特效学习 一第4张

观察效果的同时,我们也发现了BUG,放我们切换到菜单界面时,我们main_view的listview还是可以滑动的,这肯定是不符合期望的,

我们应该在菜单打开时,禁止main_view的listview滑动。

如何去禁止listviiew滑动?listview的父亲LinearLayout拦截并消费滑动事件!

根据什么判断是否拦截?根据SlideMenu的状态,是打开还是关闭。

所以要实现一个自定义的LinearLayout

在这之前,需要SlideMenu提供一个暴露状态的方法

添加方法

 /**
     * 获取当前的状态
     * @return
     */
    public DragState getCurrentState(){
        return currentState;
    }

MyLinearLayout

package com.example.xw.qqslidemenu;


import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * 当slideMenu打开的时候,拦截并消费掉触摸事件
 * 
 */
public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinearLayout(Context context) {
        super(context);
    }
    private SlideMenu slideMenu;
    public void setSlideMenu(SlideMenu slideMenu){
        this.slideMenu = slideMenu;
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(slideMenu!=null && slideMenu.getCurrentState()== SlideMenu.DragState.Open){
            //如果slideMenu打开则应该拦截并消费掉事件
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(slideMenu!=null && slideMenu.getCurrentState()== SlideMenu.DragState.Open){
            
            
            //如果slideMenu打开则应该拦截并消费掉事件
            return true;
        }
        return super.onTouchEvent(event);
    }
}

 最后将layout_main的最外层修改为我们的自定义LinearLayout

在MianActivity中根据id找到MyLinearLayout,设置setSlideMenu传入我们的SlideMenu当参数。

完成。

免责声明:文章转载自《QQ 5.0的一些特效学习 一》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇将子分支代码merge到主分支master分支Linux系统时间和硬件时间设置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

andoird软件开发之一个记录账号密码的APP--bmob后台

1.app功能:通过注册登录账户,拥有一个账户本,能够将平时自己容易的忘记的账户记录下来,并可以保持到云端,不需要担心数据丢失,只要登录账户,便可获取到自己的账户本。 2.实现的效果图,如下: 以下界面分别为注册界面、登录界面、提交账户内容界面、账户列表界面、长按删除账户信息、具体账户内容信息 3.实现的工程目录如下: 4.实现的具体过程: a.布局...

【ELK】7. elasticsearch linux上操作es命令详解

========== 1.检查ES节点是否正常启动 curl http://192.168.6.16:9200 正常状态: 非正常状态:   1>确保服务是不是正常启动了,端口用的是哪个   2>防火墙是否关闭或者端口是否开放   3>你的curl命令是否有问题,curl命令可能导致服务无法访问,可以尝试重启服务后,在外部浏览器访问UR...

ListView圆角实现

这里用到了自定义控件,自定义ListView package com.example.demo; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.AdapterV...

MongoDB 高级查询_aggregate聚合管道

MongoDB 聚合管道(AggregationPipeline) 使用聚合管道可以对集合中的文档进行变换和组合。实际项目应用主要是表关联查询、数据的统计。 MongoDB 中使用 db.COLLECTION_NAME.aggregate([{<stage>},...]) 方法 来构建和使用聚合管道。下面是官网给的实例,感受一下聚合管道的用法。...

【neo4j】简易使用说明

一、数据库简介 Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。 二、NEO4j的基本要素 1、实体节点 实体节...

项目经理打分

02章《深入C#数据类型》项目经理评分   一:创建MyOffices项目,创建UserInfo类,用来存储员工 工号,姓名,年龄,评价,年度得分 二:创建查看评分窗体(frmShow),添加定义员工数组,将员工数据绑定到frmShow窗体的ListView控件上。运行结果如下: 实现思路: 长度为3的UserInfo类型数组,并初始化数组、赋值...