一般的侧滑的实现
ViewGroup Menu + Content
onTouchEvent
MOVE:ViewGroup的leftMargin
UP:根据显示菜单的高度,决定将其隐藏或者显示
1、Scroller
2、LeftMargin + Thread
彷QQ5.0侧滑菜单的实现,使用另外的一种方法,继承HorizontalScrollView
一、自定义ViewGroup
1、构造方法的选择,获得一些需要用到的值
2、onMeasure 计算子View的宽和高,以及设置自己的宽和高
3、onLayout 决定子View的布局的位置
[4、onTouchEvent 相应手指触摸屏幕按下、抬起等事件响应 可以不实现]
----------------------------------------------------------------------
对于构造方法来说,共有三个构造方法要实现
1、context new CustomViewGroup(context)
this(Context, null);
2、context, attr 布局文件中声明(没有自定义属性)
this(context,attr,0);
3、context, attr, defStyle(有自定义属性)
super(context, attr, defStyle);
-------------------------------------------------
自定义属性
1、attr.xml
2、布局文件中 xmlns=
3、在三个参数的构造方法中,获得我们自定义的属性值
---------------------------------------------------
属性动画:
Android 3.0之后加入的
若想要在更低的版本使用需导入nineoldanimation.jar包
该包的下载路径:http://mahuiying.github.io/nineoldandroids-2.4.0.jar
第一步实现普通的侧滑菜单:
代码:
菜单(Menu)的布局文件代码 left_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/id_img1"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@id/id_img1"
android:text="第一个Item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/id_img2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@id/id_img2"
android:text="第二个Item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/id_img3"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@id/id_img3"
android:text="第三个Item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/id_img4"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@id/id_img4"
android:text="第四个Item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/id_img5"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@id/id_img5"
android:text="第五个Item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
主界面的布局代码activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:mhy="http://schemas.android.com/apk/res/com.example.mhy.qq50slidemenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/img_frame_background"
tools:context="com.example.mhy.qq50slidemenu.MainActivity"
>
<com.example.mhy.qq50slidemenu.view.SlidingMenu
android:id="@+id/sliding_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
mhy:rightPadding="100dp" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<include
layout="@layout/left_menu" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq" >
<Button
android:onClick="toggleMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切换菜单"
/>
</LinearLayout>
</LinearLayout>
</com.example.mhy.qq50slidemenu.view.SlidingMenu>
</RelativeLayout>
自定义侧滑菜单代码:SlidingMenu.java
package com.example.mhy.qq50slidemenu.view;
import android.content.Context;
import android.content.res.TypedArray;
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;
import com.example.mhy.qq50slidemenu.R;
import com.nineoldandroids.view.ViewHelper;
/**
* Created by mhy on 2016/6/9.
*/
public class SlidingMenu extends HorizontalScrollView {
private LinearLayout mWapper;
private ViewGroup mMenu;
private ViewGroup mContent;
private int mScreenWidth;
private int mMenuWidth;
//dp
private int mMenuRightPadding = 50;
private boolean once;
private boolean isOpen;
/**
* 未使用自定义属性时,调用
* @param context
* @param attrs
*/
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 当使用了自定义属性时,会调用此构造方法
* @param context
* @param attrs
* @param defStyle
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取我们定义的属性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for(int i=0; i<n; i++){
int attr = a.getIndex(i);
switch(attr){
case R.styleable.SlidingMenu_rightPadding:
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50, context
.getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
}
public SlidingMenu(Context context) {
this(context, null);
}
/**
* 设置子View的宽和高 设置自己的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!once) {
mWapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
mMenuWidth = mMenu.getLayoutParams().width =
mScreenWidth - mMenuRightPadding;
mContent.getLayoutParams().width = mScreenWidth;
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 通过设置偏移量,将menu隐藏
* 该方法在View重绘时调用
* 通过设置偏移量,将menu隐藏
* @param changed view是否改变
* @param l 相对于父布局左边的距离
* @param t 相对于父布局顶部的距离
* @param r 相对于父布局右边的距离
* @param b 相对于父布局底部的距离
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
//是View滚动到指定位置,第一个参数为所要到达的x坐标,第二个参数为所要到达的y坐标
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);
isOpen = false;
} else {
this.smoothScrollTo(0,0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单
*/
public void openMenu() {
if(isOpen) {
return;
}
this.smoothScrollTo(0, 0);
isOpen = true;
}
public void closeMenu() {
if(!isOpen)
return;
this.smoothScrollTo(mMenuWidth, 0);
}
/**
* 切换菜单
*/
public void toggle() {
if(isOpen) {
closeMenu();
} else {
openMenu();
}
}
}
MainActivity.java
package com.example.mhy.qq50slidemenu;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import com.example.mhy.qq50slidemenu.view.SlidingMenu;
public class MainActivity extends Activity {
private SlidingMenu mSlidingMenu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题拦
setContentView(R.layout.activity_main);
mSlidingMenu = (SlidingMenu) findViewById(R.id.sliding_menu);
}
public void toggleMenu(View view) {
mSlidingMenu.toggle();
}
}
实现抽屉式的菜单
在普通菜单的基础上修改SlidingMenu.java代码,修改后如下:
package com.example.mhy.qq50slidemenu.view;
import android.content.Context;
import android.content.res.TypedArray;
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;
import com.example.mhy.qq50slidemenu.R;
import com.nineoldandroids.view.ViewHelper;
/**
* Created by mhy on 2016/6/9.
*/
public class SlidingMenu extends HorizontalScrollView {
private LinearLayout mWapper;
private ViewGroup mMenu;
private ViewGroup mContent;
private int mScreenWidth;
private int mMenuWidth;
//dp
private int mMenuRightPadding = 50;
private boolean once;
private boolean isOpen;
/**
* 未使用自定义属性时,调用
* @param context
* @param attrs
*/
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 当使用了自定义属性时,会调用此构造方法
* @param context
* @param attrs
* @param defStyle
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取我们定义的属性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for(int i=0; i<n; i++){
int attr = a.getIndex(i);
switch(attr){
case R.styleable.SlidingMenu_rightPadding:
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50, context
.getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
}
public SlidingMenu(Context context) {
this(context, null);
}
/**
* 设置子View的宽和高 设置自己的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!once) {
mWapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
mMenuWidth = mMenu.getLayoutParams().width =
mScreenWidth - mMenuRightPadding;
mContent.getLayoutParams().width = mScreenWidth;
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 通过设置偏移量,将menu隐藏
* 该方法在View重绘时调用
* 通过设置偏移量,将menu隐藏
* @param changed view是否改变
* @param l 相对于父布局左边的距离
* @param t 相对于父布局顶部的距离
* @param r 相对于父布局右边的距离
* @param b 相对于父布局底部的距离
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
//是View滚动到指定位置,第一个参数为所要到达的x坐标,第二个参数为所要到达的y坐标
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);
isOpen = false;
} else {
this.smoothScrollTo(0,0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单
*/
public void openMenu() {
if(isOpen) {
return;
}
this.smoothScrollTo(0, 0);
isOpen = true;
}
public void closeMenu() {
if(!isOpen)
return;
this.smoothScrollTo(mMenuWidth, 0);
}
/**
* 切换菜单
*/
public void toggle() {
if(isOpen) {
closeMenu();
} else {
openMenu();
}
}
/**
* 滚动发生时
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float scale = l*1.0f/mMenuWidth;
//调用属性动画,设置TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth*scale);
}
}
第三部实现最终得QQ5.0侧滑
修改第二步中代码,即修改SlidingMenu.java代码,修改后如下:
package com.example.mhy.qq50slidemenu.view;
import android.content.Context;
import android.content.res.TypedArray;
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;
import com.example.mhy.qq50slidemenu.R;
import com.nineoldandroids.view.ViewHelper;
/**
* Created by mhy on 2016/6/9.
*/
public class SlidingMenu extends HorizontalScrollView {
private LinearLayout mWapper;
private ViewGroup mMenu;
private ViewGroup mContent;
private int mScreenWidth;
private int mMenuWidth;
//dp
private int mMenuRightPadding = 50;
private boolean once;
private boolean isOpen;
/**
* 未使用自定义属性时,调用
* @param context
* @param attrs
*/
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 当使用了自定义属性时,会调用此构造方法
* @param context
* @param attrs
* @param defStyle
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取我们定义的属性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for(int i=0; i<n; i++){
int attr = a.getIndex(i);
switch(attr){
case R.styleable.SlidingMenu_rightPadding:
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50, context
.getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
}
public SlidingMenu(Context context) {
this(context, null);
}
/**
* 设置子View的宽和高 设置自己的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!once) {
mWapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
mMenuWidth = mMenu.getLayoutParams().width =
mScreenWidth - mMenuRightPadding;
mContent.getLayoutParams().width = mScreenWidth;
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 通过设置偏移量,将menu隐藏
* 该方法在View重绘时调用
* 通过设置偏移量,将menu隐藏
* @param changed view是否改变
* @param l 相对于父布局左边的距离
* @param t 相对于父布局顶部的距离
* @param r 相对于父布局右边的距离
* @param b 相对于父布局底部的距离
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
//是View滚动到指定位置,第一个参数为所要到达的x坐标,第二个参数为所要到达的y坐标
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);
isOpen = false;
} else {
this.smoothScrollTo(0,0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单
*/
public void openMenu() {
if(isOpen) {
return;
}
this.smoothScrollTo(0, 0);
isOpen = true;
}
public void closeMenu() {
if(!isOpen)
return;
this.smoothScrollTo(mMenuWidth, 0);
}
/**
* 切换菜单
*/
public void toggle() {
if(isOpen) {
closeMenu();
} else {
openMenu();
}
}
/**
* 滚动发生时
*/
@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:内容区域1.0 ~ 0.7 缩放效果scale : 1.0 ~ 0.0 0.7 + 0.3 * scale
* 区别2:菜单的偏移量需要修改
* 区别3:菜单的显示时有缩放以及透明度变化,缩放: 0.7 ~ 1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0
* 0.6 + 0.4 *(1-scale)
*/
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);
//设置content的缩放的中心点
ViewHelper.setPivotX(mContent, 0);
ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
}
}