一、概述
一般的侧滑实现:
ViewGroup Menu+Content
onTouchEvent监听器可以用来监听手指抬起,点击等事件
MOVE:不断监听用户的移动距离,改变ViewGroup的leftMargin
UP:对用户的操作进行判断,判断是大于一半还是小于一半,若大于则显示菜单,若小于则隐藏菜单(即根据显示菜单的宽度,决定将其隐藏还是显示)
1、Scroller辅助类实现动画效果
2、LeftMargin+Thread
换个思路:继承HorizontalScrollerView(水平滚动条),好处:无需判断高度只需考虑水平宽度,可以省去MOVE冲突的处理。
二、创建安卓工程QQ50SlidingMenu
菜单布局文件:
新建布局文件:left_menu.xml
Strings.xml:
QQ侧滑菜单
Settings
Hello world!
赵灵儿
林月如
阿奴
韩菱纱
唐雨柔
切换菜单
布局文件:
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:id="@+id/img1"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/img_1"
android:layout_centerVertical="true"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="@string/txt1"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_toRightOf="@+id/img1"
android:layout_centerVertical="true"
/>
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:id="@+id/img2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/img_2"
android:layout_centerVertical="true"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="@string/txt2"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_toRightOf="@+id/img2"
android:layout_centerVertical="true"
/>
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:id="@+id/img3"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/img_3"
android:layout_centerVertical="true"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="@string/txt3"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_toRightOf="@+id/img3"
android:layout_centerVertical="true"
/>
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:id="@+id/img4"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/img_4"
android:layout_centerVertical="true"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="@string/txt4"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_toRightOf="@+id/img4"
android:layout_centerVertical="true"
/>
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:id="@+id/img5"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/img_5"
android:layout_centerVertical="true"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:text="@string/txt5"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_toRightOf="@+id/img5"
android:layout_centerVertical="true"
/>
接下来完成主布局文件:mainAActivity.xml文件:
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/girl">
>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq"
>
自定义View:SlidingMenu.java,继承水平滚动控件类
1、首先实现其两个参数的构造方法
2、将自定义的HorizontalScrollView名字改成自定义的属性名 包名+类名
…………………………
>
3、自定义ViewGroup:
(1)、onMeasure:决定内部View(子View)的宽和高以及自己的宽和高
(2)、onLayout:决定子View放置的位置
(3)、onTouchEvent:判断用户的手指状态
package org.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
public class SlidingMenu extends HorizontalScrollView {
private LinearLayout mWrapper;//固定写法,水平区域内只存在一个LinearLayout
private ViewGroup mMenu;
private ViewGroup mContent;
private int mScreenWidth;//屏幕宽度
private int mMenuRightPadding=50;//定义菜单与屏幕右侧的距离
private boolean once;//onMeasure()方法可能不止一次被调用
private int mMenuWidth;//菜单界面的宽度
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager wm=(WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//屏幕宽度可以通过上下文对象进行获取
DisplayMetrics outMetrics=new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth=outMetrics.widthPixels;
mMenuRightPadding=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50, context.getResources().getDisplayMetrics());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!once){
mWrapper=(LinearLayout) this.getChildAt(0);//使用静态方法getChildAt(0)获取线性布局的属性值
mMenu=(ViewGroup) mWrapper.getChildAt(0);//获取内部菜单
mContent=(ViewGroup) mWrapper.getChildAt(1);
mMenuWidth=mMenu.getLayoutParams().width=mScreenWidth-mMenuRightPadding;//菜单宽度等于屏幕宽度减去菜单距离屏幕右侧的宽度
mContent.getLayoutParams().width=mScreenWidth;
once=true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed){
this.scrollTo(mMenuWidth,0);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action=ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX=getScrollX();//屏幕多出的部分(隐藏在左边的宽度)
if(scrollX>=mMenuWidth/2){
this.smoothScrollTo(mMenuWidth, 0);//慢慢移动,有动画感觉
}else{
this.smoothScrollTo(0, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
}
运行效果:
但这里有一个小问题,上顶部的黑色内容区域没有隐藏:
在mainAActivity中加入:
requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏黑色信息区域
运行效果:
拖动后的效果:
三、用户自定义属性
至此,侧滑已经实现。接下来加入自定义属性,可以让用户去设置菜单离屏幕右边的边距。
(1)、在values文件夹中创建一个xml文件,叫做userattr.xml
(2)注意:在xml文件中编写自定义属性时是没有代码提示功能的,因此要注意代码拼写
(3)接下来要在mainAActivity.xml文件中命名空间进行声明:
xmlns:Alan="http://schemas.android.com/apk/res/com.example.qq50slidingmenu"
注意:这里的格式是xmlns:自己编写,可以是任何名字="http://schemas.android.com/apk/res/当前应用的包名,而不是View的包名"
(4)、接下来将自定义属性放到View中进行使用
Alan:rightPadding="100dp"
(5)、在刚才未使用自定义属性时,调用自身的构造方法,定义之后,需要生成另外两个构造方法:将原来两个参数构造方法中的方法拷贝到三个参数的构造方法之中,然后将两个参数构造方法的第三个参数设置为0,将一个参数的构造方法(传入上下文对象方法)的第二个参数设置为null,即调用两个参数的构造方法。
(6)、获取我们定义的属性:在三个参数的构造方法中获取:通过TypedArray这个类获得自定义属性,这里需要注意的是TypedArray这个类用完之后需要进行释放(recycled)。
源代码:
activityMain.xml:
xmlns:tools="http://schemas.android.com/tools"
xmlns:Alan="http://schemas.android.com/apk/res/com.example.qq50slidingmenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_width="match_parent"
android:layout_height="match_parent"
Alan:rightPadding="200dp"
>
View代码:
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取我们定义的属性
TypedArray array=context.getApplicationContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyle, 0);
int n=array.getIndexCount();//获取自定义属性数量
for(int i=0;i
int attr=array.getIndex(i);
switch (attr) { //只有一个
case R.styleable.SlidingMenu_rightPadding:
mMenuRightPadding=array.getDimensionPixelSize(attr,(int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50,
context.getResources().getDisplayMetrics()));
//若无自定义属性,则使用原始的50px
}
}
array.recycle();//释放操作
WindowManager wm=(WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//屏幕宽度可以通过上下文对象进行获取
DisplayMetrics outMetrics=new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth=outMetrics.widthPixels;
//mMenuRightPadding=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50, context.getResources().getDisplayMetrics());
}
public SlidingMenu(Context context) {
super(context,null);//调用两个参数的构造方法
}
四、为侧滑菜单添加一个按钮,当用户点击时,侧滑菜单自动出现,再点击时自动消失
(1)、首先定义一个bool类型的变量标识当前的状态:
Boolean isOpen; //默认打开时为false
(2)、划开之后设置为true,未划开为flase
public boolean onTouchEvent(MotionEvent ev) {
int action=ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX=getScrollX();//屏幕多出的部分(隐藏在左边的宽度)
if(scrollX>=mMenuWidth/2){
this.smoothScrollTo(mMenuWidth, 0);//慢慢移动,有动画感觉
isOpen=false;
}else{
this.smoothScrollTo(0, 0);
isOpen=true;
}
return true;
}
return super.onTouchEvent(ev);
}
(3)、新增打开、关闭、切换菜单方法:
public void openMenu(){
if(isOpen) return;
this.smoothScrollTo(0, 0);
isOpen=true;
}
public void closeMenu(){
if(!isOpen) return;
this.smoothScrollTo(mMenuWidth, 0);
isOpen=false;
}
public void transform(){
if(isOpen){
closeMenu();
}else{
openMenu();
}
}
(4)、在主布局文件中添加一个button:
切换菜单
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq"
>
android:onClick="transformMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/transform"
/>
(5)、在MainActivity.java中添加onClick方法,并为自定义菜单设置一个id用来获取并使用它:
android:id="@+id/id_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
Alan:rightPadding="1000dp"
>
public class MainActivity extends Activity {
private SlidingMenu mLeftMenu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏黑色信息区域
setContentView(R.layout.activity_main);
mLeftMenu=(SlidingMenu) findViewById(R.id.id_menu);
}
public void transformMenu(View view){
mLeftMenu.transform();
}
运行效果:
点击后:
-------------------------------------------------------------------------------------------------------
五、抽屉式菜单:
效果:菜单在内容区的下方
mMenuWidth
100px mMenuWidth-100
200px mMeunWidth-200
在这里需要用到属性动画相关知识:
TranslationX
getScrollX:mMenuWidth~0
设置调用动画时机
ACTION_MOVE
由于属性动画是从Android 3.0开始才引入进来,则我们需要导入一个兼容性jar包nineoldandroids.jar
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float scale=l*1.0f/mMenuWidth;//1~0
//调用属性动画,设置TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth*scale);
}
六、实现QQ5.0侧滑立体效果
区别1:内容区域范围:1.0~0.7缩放的效果
区别2:菜单的偏移量需要修改
区别3:菜单的显示时有缩放以及透明度的变化
Scale:1.0~0
0.7+0.3*scale 缩放比例
菜单显示缩放:0.7~1.0
1.0-scale*0.3
透明度:0.6~1.0
0.6+0.4*(1-scale)
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float scale=l*1.0f/mMenuWidth;//1~0
float rightScale=0.7f+0.3f*scale ;
//调用属性动画,设置TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth*scale);
ViewHelper.setPivotX(mContent, 0);//设置中心点
ViewHelper.setPivotY(mContent, mContent.getHeight()/2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
}
float rightScale=0.7f+0.3f*scale ;
float leftScale=1.0f-scale*0.3f;//左部菜单缩放
float leftAlpha=0.6f+0.4f*(1-scale);//左侧透明度变化
//调用属性动画,设置TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth*scale);
ViewHelper.setScaleX(mMenu, leftScale);
ViewHelper.setScaleY(mMenu, leftScale);
ViewHelper.setAlpha(mMenu, leftAlpha);
ViewHelper.setPivotX(mContent, 0);//设置中心点
ViewHelper.setPivotY(mContent, mContent.getHeight()/2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
但此时发现一个问题,当刚开始拖动时,有一部分内容隐藏在屏幕左侧,没有立即显示出来,给人感觉不像是直接在内容界面的下面。
//调用属性动画,设置TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth*scale*0.7f);
运行结果:
滑动前:
七、总结
1、自定义ViewGroup:
构造方法的选择,获得一些需要用到的值
onMeasure计算子View的宽和高,以及设置自己的宽和高
onLayout决定子View的布局的位置
onTouchEvent可选
2、构造方法
Context new SustomViewGroup(context)
This(context,null)
Context ,attr 布局文件中声明(没有自定义的属性)
This(context,attr,0)
Context ,attr,defStyle(有自定义的属性)
(3)、自定义属性
Attr.xml
布局文件中 xmlns=
在3个参数的构造方法中,获得自定义属性的值
属性动画:
nineold