已经好久没写过Android开发的技术博客,只因最近项目比较急。耽误了。今天带来的QQ5.0侧滑效果。我们都对QQ5.0侧滑效果很熟悉了,就不多做介绍,就一个字“炫”。正好这次在项目就需要用这个效果,所以就拿出来给大家分享一下。也许大家认为都看了很多了,确实但是相同的效果用在不同的环境,也许你会遇到一些不一样的问题,那样你将收获到不同的东西。就比如我这次使用的时候就出现了一个HorizontalScrollView与Viewpager冲突的问题。就是我的菜单界面就是侧滑界面,然后主界面是使用了Vewpager+Fragment滑动的,然后主界面和侧滑界面就是放在同一个HorizontalScrollView中的。侧滑是实现了,后来发现ViewPager中的Fragment划不动了。所以等下我会具体讲解一下解决办法。
实现QQ5.0侧滑效果可以很多办法实现,比如可以使用一些开源框架,自定义组件。当然使用别人写好的框架固然没什么错,但是你就不想自己掌握其真正的原理吗?相信大家都是渴望对技术热爱与追求吧。如果你是一个Geek那就和我一起来了解一下华丽侧滑效果的原理吧。欢迎大家提出宝贵意见。废话不多说,开始吧
我这次主要是通过自定义控件来实现高仿QQ5.0侧滑效果的。
实现该效果的大概思想是这样的:就是将侧滑菜单的界面和主界面同时放在一个继承于HorizontalScrollView的一个自定义组件中,然后通过实时获得系统设备屏幕的宽度(主要通过重写onMeasure()方法),当我们在滑动的时候onMeasure方法会实时测量此时屏幕的宽度,也就是说屏幕的宽度会随着滑动变化而变化,为什么要获取设备的宽度呢??因为只有获取到设备宽度后,然后分别设置菜单界面宽度占屏幕宽度多少比例,主界面宽度占屏幕宽度多少比例。高度就不需要了因为高度都是一样占满屏幕,获取到屏幕的宽度后,然后按一定比例分配宽度给左菜单视图和主视图。然后把这个各自分配宽度设置到两个视图中,设置好宽度后就是定义好两个视图的位置,这个是通过重写onLayout方法来实现,该方法有个非常重要的参数就是个Boolean类型的changed参数,该参数默认是false,该参数表示默认就是初始的位置,初始位置就是左菜单占屏幕80%,而主界面占20%,那这不是我们想要的结果,我想要的结果是一进来是左菜单隐藏,而主界面占屏幕100%,所以需要对changed的值进行修改。然后通过调用smoothScrollTo(left_menu_width, 0)表示显示主界面,smoothScrollTo(0, 0)则表示显示左菜单(显示)和主界面(隐藏),先说下left_menu_width是什么,它表示左菜单的宽度。smoothScrollTo(left_menu_width, 0)表示从0到left_menu_width的位移,此时表示手指向左滑动主界面全部显示左菜单隐藏,那么主界面需要滑到的位置也就是left_menu_width(是从左菜单显示主菜单隐藏的位置滑到主菜单显示左菜单隐藏的位置),那么smoothScrollTo(0, 0)表示一直左菜单正好全部滑到屏幕的左下角(0,0)位置的时候全部显示左菜单。那么如何判别是左滑动还是右滑动呢?主要就是重写了一个onTouchEvent的方法,通过Action等于down的时候,表示手指按下,获得此时的在X轴上的坐标,然后再监听Action等于UP的时候,通过getScrollX()方法获得到从down到up事件转变所发生的位移,做个这样的设置若位移大于了左菜单的一半时候,并且为正数时,表示此时向右滑动,我们就是直接滑到左菜单显示主界面隐藏的位置也就是smoothScrollTo(0, 0),若小于左菜单的一半时候,即表示此时为负的,向左滑动,即此时向左滑动,左菜单隐藏,主界面显示也就是smoothScrollTo(left_menu_width, 0)。这样就基本实现了侧滑的原理,但是并没有做完,因为要华丽的侧滑还远远不够,因为需要在侧滑的过程中加入属性动画,当向左滑动的时候,左菜单透明度变小,字体缩放,位移变小,主界面透明度变大,字体扩大。这里对于属性动画我就直接引用一个第三方的jar包,nineoldandroids-2.4.0.jar。那么如何去控制缩放的比例的呢?别忘我们前面有个屏幕宽度,因为它是在随着滑动宽度在不断变化,从而就形成一个动态变化的比例。巧妙的利用这个比例,可以做出很多意想不到的效果出来。
下面请看代码:Activity_menu.xml总体布局代码:
<com.zhongqihong.slidingmenu.MySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/left_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:fadingEdge="none"
android:background="@drawable/left_menu_bg"
android:scrollbars="none" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" >
<!-- 菜单页面 -->
<include layout="@layout/activity_left" />
<!-- 主界面 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFF"
android:orientation="vertical" >
<include
android:id="@+id/includes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/header_menu" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="9" >
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</android.support.v4.view.ViewPager>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="#eee"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv_icon_first"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_weight="2"
android:scaleType="centerInside"
android:src="@drawable/first2" />
<TextView
android:id="@+id/tv_text_first"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="首页"
android:textColor="#FF6600"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv_icon_second"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_weight="2"
android:scaleType="centerInside"
android:src="@drawable/two" />
<TextView
android:id="@+id/tv_text_second"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="收入"
android:textColor="#fdaf33"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv_icon_three"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_weight="2"
android:scaleType="centerInside"
android:src="@drawable/three" />
<TextView
android:id="@+id/tv_text_three"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="支出"
android:textColor="#fdaf33"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv_icon_four"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_weight="2"
android:scaleType="centerInside"
android:src="@drawable/four" />
<TextView
android:id="@+id/tv_text_four"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="便签"
android:textColor="#fdaf33" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
自定义框架MySlidingMenu.java
<pre name="code" class="java">package com.zhongqihong.slidingmenu;
import com.nineoldandroids.view.ViewHelper;
import com.zhongqihong.loadpager.R;
import android.R.bool;
import android.R.integer;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
/**
* @author zhongqihong
* 自定义侧滑框架
* 集成HorizontalScrollView高级控件并且重写两个参数的构造方法
* */
public class MySlidingMenu extends HorizontalScrollView {
private int widthPixel;//屏幕的宽度
private ViewGroup left_menu;
private ViewGroup main_menu,main_menu_ll;
private int left_menu_width;
private int downX;
private int main_menu_width;
private float LEFT_MENU_PERCENT=0.7f;
private boolean isOpen;
private ViewPager vp;
private ImageView Bariv;
public MySlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
//获取屏幕的宽度
//先获取到窗体的管理器,然后通过窗口管理器去测量屏幕的宽度
WindowManager manager=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//获取窗体管理器
//拿到屏幕的显示器
DisplayMetrics metrics=new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metrics);
widthPixel=metrics.widthPixels;
}
/**
* @author zhongqihong
* 当我们滑动HorizontalScrollView时,会发现Viewpager不能再滑动了,这就是所谓HorizontalScrollView与Viewpager产生了冲突
* 解决办法:
* 在HorizontalScrollView类或者子类中重写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent方法,并且加上
* 在重写的每个方法中加入 vp.requestDisallowInterceptTouchEvent(true);代码,vp就是一个ViewPager滑动的时候的对象
* */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
vp.requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
vp.requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
//测量视图高度和宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
LinearLayout ll=(LinearLayout) this.getChildAt(0);//拿到当前视图中的整个布局的第一个子视图,最外层的线性布局
left_menu= (ViewGroup) ll.getChildAt(0);//线性布局中的第一个子视图就是左边菜单布局
main_menu=(ViewGroup) ll.getChildAt(1);//线性布局中的第二个子视图就是主菜单布局
//接着就是给他们设置宽度
main_menu_ll=(ViewGroup)main_menu.getChildAt(1);//获得主菜单中的线性布局
vp=(ViewPager)main_menu_ll.getChildAt(0);//在主菜单的线性布局中再获取Viewpager对象
Bariv=(ImageView) main_menu.getChildAt(0).findViewById(R.id.includes).findViewById(R.id.iv_to_slidmenu);//获取到header_menu.xml中的iv_slidingMenu控件对象
left_menu.getLayoutParams().width=(int) (widthPixel*LEFT_MENU_PERCENT);//左菜单占整个屏幕的80%
left_menu_width=(int) (widthPixel*LEFT_MENU_PERCENT);
main_menu.getLayoutParams().width=widthPixel;//主菜单占整个屏幕
//main_menu_width=widthPixel;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//设置子视图的位置,重写onlayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//默认change为false,默认两个子视图的位置,就是初始时给属性宽度赋值的状态,是一个左菜单视图占屏幕的80%,而主菜单则占
//20%,故此这并不是我们想要的结果,我们想先看到的是主菜单,然而changed这个boolean类型的变量控制的,所以首先赋值为true使得
//首先看到的是主界面
changed=true;//
if (changed) {
this.smoothScrollTo(left_menu_width, 0);
}
super.onLayout(changed, l, t, r, b);
}
//手滑动的时候,重写滑动时候方法
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
//l表示:滑动时候左边滑动的位移,top上面的位置,oldl表示之前没右滑动的时候的位置
//加入属性动画、l左边位移的范围是0~left_menu_width
float scale=(float)l/left_menu_width;//这就是缩放的比例,是根据位移的变化而变化的(缩放范围:先是从1再变到0,以为初始位置是左菜单隐藏,left_menu_width为左菜单的宽度,左边的位移也是左菜单的宽度)
//左边菜单缩放
float leftScale=1.0f-scale*0.3f;//缩放比例的范围:1-0.7
ViewHelper.setScaleX(left_menu, leftScale);
ViewHelper.setScaleY(left_menu, leftScale);
//透明度变化
float leftAlpha= 0.6f + 0.4f * (1 - scale);
ViewHelper.setAlpha(left_menu, leftAlpha);
//位移变化
ViewHelper.setTranslationX(left_menu, left_menu_width * scale * 0.8f);
//主菜单的缩放功能 缩放比例0.8~1
float rightScale=0.7f + 0.3f * scale;
// 设置content的缩放的中心点
ViewHelper.setPivotX(main_menu, 0);
ViewHelper.setPivotY(main_menu, main_menu.getHeight() / 2);
ViewHelper.setScaleX(main_menu, rightScale);
ViewHelper.setScaleY(main_menu, rightScale);
/*使得当滑动的时候,利用缩放比例scale这个动态变量,左上角的图标会随着滑动旋转的角度而变化 */
float rotateDegree=(1-scale)*45.0f;//
Bariv.setRotation(rotateDegree);
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
vp.requestDisallowInterceptTouchEvent(true);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN://按下事件,取得按下时候X的坐标,通过按下的和抬起的时候的位移大小来判断
downX=(int) ev.getX();
case MotionEvent.ACTION_UP://抬起事件,获取抬起的事件
int d=getScrollX();//拿到了在X轴上滑动的位移
if(d<left_menu_width/2){//若滑动的位移大于左菜单的一半时表示此时向右滑动,并且就直接滑到左菜单占屏幕的70%
this.smoothScrollTo(0, 0);
isOpen=true;
}else{
this.smoothScrollTo(left_menu_width, 0);
isOpen=false;
}
return true;
}
return super.onTouchEvent(ev);
}
//打开菜单
public void openMenu(){
if (isOpen) {//isOpen为true,则表明此时的左菜单已经打开的状态就直接结束函数
return ;
}
else{//ipOpen为false,则表明此时的左菜单处于关闭状态,就需要打开
this.smoothScrollTo(0, 0);//左菜单出来
isOpen=true;//并把状态修改为打开状态
}
}
//关闭菜单
public void closeMenu(){
if (!isOpen) {//isOpen为false,则表明此时的左菜单已经关闭的状态就直接结束函数
return;
}else{//ipOpen为true,则表明此时的左菜单处于打开状态,就需要关闭
this.smoothScrollTo(left_menu_width, 0);//左菜单关闭
isOpen=false;//并把状态修改为关闭状态
}
}
//切换菜单的按钮
public void toggle() {
// TODO Auto-generated method stub
if (isOpen) {//打开状态,就要关闭,调用closeMenu()
closeMenu();
}else{//关闭状态,就要打开,调用openMenu()
openMenu();
}
}
}
解决办法如下:再重写两个方法dispatchTouchEvent,onInterceptTouchEvent在它们两个和已经重写的onTouchEvent方法中加入:
vp.requestDisallowInterceptTouchEvent(true);代码,vp也就是一个ViewPager滑动对象。就能解决此问题。