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

效果:

源码点我

导入的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>

预览效果:

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>

预览

最后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);
}
}); }
}

效果

观察效果的同时,我们也发现了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的一些特效学习 一的更多相关文章

  1. 腾讯QQ空间穿越时光轴3D特效

    <DOCTYPE html> <html> <head> <title>腾讯QQ空间穿越光轴3D特效</title> <style&g ...

  2. Cocos2d-X研究之3.0 场景切换特效汇总

    Cocos2d-X研究之3.0 场景切换特效汇总 2014-08-05      0个评论    来源:游戏编程    收藏    我要投稿 cocos2d-x 3.0中场景切换特效比较多,而且游戏开 ...

  3. QQ 特效学习 二 侧滑删除

    上篇文章: http://www.cnblogs.com/xurui1995/p/5798631.html 今天来写不仅是qq而且在别的软件上也特别流行的侧滑删除 其实套路和前篇的一样,一个自定义Vi ...

  4. Three.js 3D特效学习

    一.Three.js基本介绍 Three.js是JavaScript编写的WebGL第三方库.提供了非常多的3D显示功能.Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场 ...

  5. 《VC++ 6简明教程》即VC++ 6.0入门精讲 学习进度及笔记

    VC++6.0入门→精讲 2013.06.09,目前,每一章的“自测题”和“小结”三个板块还没有看(备注:第一章的“实验”已经看完). 2013.06.16 第三章的“实验”.“自测题”.“小结”和“ ...

  6. Visual Studio 2015 Owin+MVC+WebAPI+ODataV4+EntityFrawork+Identity+Oauth2.0+AngularJS 1.x 学习笔记

    2016年,.net 会有很多大更新 ASP.NET 5 在此之前我都是用着古老的.net做开发的 (WebForm + IIS) 为了接下来应对 .net 的新功能,我特地去学习了一下基本的 MVC ...

  7. OSG报警特效学习总结

    方法一:粒子系统         OSG的粒子系统有自己定义好的模块,如osgParticle::ExplosionEffect(爆炸模拟):osgParticle::SmokeEffect(烟雾模拟 ...

  8. 从0开始的Python学习019更多的Python内容2

    书接上文,接演Python全传 话说学了这么多Python的基础知识,也该写一点让别人看不懂的代码了. lambda lambda表达式,是一个方法的简化形似,它没有自己的代码块,它后面的语句就是它的 ...

  9. 从0开始的Python学习018更多的Python内容

    特殊的方法 之前学习的都是一些常用的方法,为了使我们的学习更加的完整,我们在这里学习一些特殊的方法. 一般说来,特殊的方法都被用来模仿某个行为.例如,如果你想要为你的类使用x[key]这样的索引操作( ...

随机推荐

  1. Hadoop框架基础(五)

    ** Hadoop框架基础(五) 已经部署了Hadoop的完全分布式集群,我们知道NameNode节点的正常运行对于整个HDFS系统来说非常重要,如果NameNode宕掉了,那么整个HDFS就要整段垮 ...

  2. 程序Yuan,eclipse你,会用吗?

    程序Yuan,用eclipse敲代码时还在使用alt+/吗,还在为格式化代码ctrl+shift+f快捷键不快捷发愁吗? 如果是,那么这篇就适合你,请继续往下看. ①设置代码提示快捷键         ...

  3. 初学者指南:ZFS 是什么,为什么要使用 ZFS?

    作者: John Paul 译者: LCTT Lv Feng 今天,我们来谈论一下 ZFS,一个先进的文件系统.我们将讨论 ZFS 从何而来,它是什么,以及为什么它在科技界和企业界如此受欢迎. 虽然我 ...

  4. 我的PHP学习之路

    由于工作中,做微信小程序需要我自己写一些后台代码.并且公司后台用的是php.所以我决定在周末和下班后抽空学习php.一开始,我想找一些入门视频来学,然后发现好像效率不是很好.不如看书来得痛快.(主要是 ...

  5. 由于webpack-cli版本问题造成的错误

    The CLI moved into a separate package: webpack-cli Please install 'webpack-cli' in addition to webpa ...

  6. Android中图片优化之webp使用

    博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 有关图片的优化,通常我们会用到LruCache(使用强引用.强 ...

  7. ArcGIS api for javascript——查询没有地图的数据

    描述 本例展示了用户能够从没有显示服务的地图服务查询数据.大部分地图服务包含属性信息的数据集,数据集能够被查询并显示在一个简单的列或表格里. 本例按提供的州名称查询USA人口普查数据,然后显示关于州的 ...

  8. [Python] Normalize the data with Pandas

    import os import pandas as pd import matplotlib.pyplot as plt def test_run(): start_date='2017-01-01 ...

  9. android 2048游戏实现

    android 的2048小游戏完整实现:GridLayout布局(android 4.0及以上). 曾经做过一个2048的算法题,学了几天android,认为能够实现个安卓版的.也就动手写了个. 包 ...

  10. thinkphp5项目--企业单车网站(三)

    thinkphp5项目--企业单车网站(三) 项目地址 fry404006308/BicycleEnterpriseWebsite: Bicycle Enterprise Websitehttps:/ ...