在公司做开发的时候接到一个任务就是实现侧滑效果(酷狗比较老的版本的那种策划效果,QQ6.0以上的版本也是侧滑效果),遇到这种侧滑效果的话怎么实现?
首先应该考虑到以下几种办法:
1,使用系统自带的DrawerLayout这个控件
2,自定义ViewGroup+手势处理类(实现起来代码量有点多)
3,自定义ScrollView
在这里我们使用第三种方式自定义ScrollView的方式来实现。首先我们可以直观的想一下侧滑效果的布局,手指按在屏幕上往右滑,此时下层的布局就会展示出来,并且由小到大,上面的能够往右滑的这个布局逐渐变小,现在也就明白了,我们需要定义两个布局,假设上面的布局叫做内容布局,命名为layout_home_content,下面的布局命名为layout_home_menu,先暂时不考虑缩放问题,当前首要的任务就是把两个布局实现,并且能够滑动。下面是两个布局的具体实现代码:
layout_home_content.xml(这个布局很简单,只放了一个TextView)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_centerInParent="true"
android:text="主页内容"
android:layout_height="wrap_content" />
</RelativeLayout>
layout_home_menu.xml(这个布局相对复杂一点,主要分为三大块,最上面是用户信息,中间放了一个ListView来显示能够点击的条目,最下面是退出按钮)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="72dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/enter_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="23dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/user_head_iv"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/morentouxiang" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22dp"
android:orientation="vertical">
<TextView
android:id="@+id/user_name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:drawableRight="@drawable/user_write_paint"
android:text="请登录"
android:textColor="#c6b178"
android:textSize="18dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="42dp"
android:orientation="horizontal">
<TextView
android:id="@+id/user_attention_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:text="关注 0"
android:textColor="#c6b178"
android:textSize="12dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="40dp"
android:drawablePadding="10dp"
android:text="粉丝 0"
android:textColor="#c6b178"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<ListView
android:id="@+id/menu_item_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:dividerHeight="0dp"
android:layout_marginTop="60dp"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:text="退出"
android:textColor="#FFFFFF"
android:layout_height="wrap_content" />
</RelativeLayout>
在定义完了上面两个布局之后,在主布局activity_main中引用这两个布局,其中SlidingMenu就是我们自定义的继承自ScrollView的滑动布局,如果你的包名和我的不一样,需要修改为你的完整包名。
<com.example.myproject.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/home_sliding_menu"
app:menuRightMargin="62dp"
android:background="@drawable/home_menu_bg"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include layout="@layout/layout_home_menu" />
<include layout="@layout/layout_home_content" />
</LinearLayout>
</com.example.myproject.SlidingMenu>
好了,现在我们把布局绘制完成了(其中布局中的图片资源不再上传,大家可以随便找一个代替),现在运行一下:
为什么出现这样的结果呢?home_content布局这么窄的原因是因为home_content布局的宽高自定为wrap_content,放放在自定义的ScrollView中形成的。
接下来开始解决问题:
1,自定内容页面的宽度:
这里需要用到一个onFinishInflate(),这个方法是在布局解析完成后调用
rt android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
/**
* Created by yly on 2019/8/10.
*/
public class SlidingMenu extends HorizontalScrollView {
private int menuWidth;
public SlidingMenu(Context context) {
super(context);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义的右边一小块的宽度
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
float rightMargin=typedArray.getDimension(R.styleable.SlidingMenu_menuRightMargin,dip2px(context,50));
typedArray.recycle();
menuWidth= (int) (getScreenWidth(context)-rightMargin);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//1,指定内容页面的宽度是整个屏幕的宽度,
ViewGroup container= (ViewGroup) getChildAt(0);//这是SlidingMenu布局下的第一层LinearLayout布局
int childCount=container.getChildCount();
if (childCount>2){
throw new RuntimeException("不能放置超过两个布局");
}
// 2,菜单页面的宽度等于屏幕的宽度减去右边一小步分的宽度(menuRightMargin)
View menuView=container.getChildAt(0);
ViewGroup.LayoutParams menuLayoutParams=menuView.getLayoutParams();
menuLayoutParams.width=menuWidth;
menuView.setLayoutParams(menuLayoutParams);
//7.0以下的手机不加这一行是可以的,但是高版就不行了
View contentView=container.getChildAt(1);
ViewGroup.LayoutParams contentLayoutParams=contentView.getLayoutParams();
contentLayoutParams.width=getScreenWidth(getContext());
contentView.setLayoutParams(contentLayoutParams);
}
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 获得屏幕高度
*
* @param context
* @return
*/
private int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
}
好了,现在运行一下,内容页面正常显示了。接下来处理页面滑动,和缩放问题:(代码中有详细的注释)
package com.example.myproject;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
/**
* Email 240336124@qq.com
* Created by Darren on 2017/6/19.
* Version 1.0
* Description:
*/
public class SlidingMenu extends HorizontalScrollView {
// 菜单的宽度
private int mMenuWidth;
private View mContentView,mMenuView;
// GestureDetector 处理快速滑动
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightMargin = array.getDimension(
R.styleable.SlidingMenu_menuRightMargin, dip2px(context, 50));
// 菜单页的宽度是 = 屏幕的宽度 - 右边的一小部分距离(自定义属性)
mMenuWidth = (int) (getScreenWidth(context) - rightMargin);
array.recycle();
}
// 1.宽度不对(乱套了),指定宽高
@Override
protected void onFinishInflate() {
// 这个方法是布局解析完毕也就是 XML 布局文件解析完毕
super.onFinishInflate();
// 指定宽高 1.内容页的宽度是屏幕的宽度
// 获取LinearLayout
ViewGroup container = (ViewGroup) getChildAt(0);
int childCount = container.getChildCount();
if (childCount != 2) {
throw new RuntimeException("只能放置两个子View!");
}
mMenuView = container.getChildAt(0);
// 设置只能通过 LayoutParams ,
ViewGroup.LayoutParams menuParams = mMenuView.getLayoutParams();
menuParams.width = mMenuWidth;
// 7.0 以下的手机必须采用下面的方式
mMenuView.setLayoutParams(menuParams);
// 2.菜单页的宽度是 屏幕的宽度 - 右边的一小部分距离(自定义属性)
mContentView = container.getChildAt(1);
ViewGroup.LayoutParams contentParams = mContentView.getLayoutParams();
contentParams.width = getScreenWidth(getContext());
mContentView.setLayoutParams(contentParams);
// 2. 初始化进来是关闭 发现没用
// scrollTo(mMenuWidth,0);
}
// 4. 处理右边的缩放,左边的缩放和透明度,需要不断的获取当前滚动的位置
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
Log.e("TAG", "l -> " + l);// 变化是 mMenuWidth - 0
// 算一个梯度值
float scale = 1f * l / mMenuWidth;// scale 变化是 1 - 0
// 右边的缩放: 最小是 0.7f, 最大是 1f
float rightScale = 0.7f + 0.3f * scale;
// 设置右边的缩放,默认是以中心点缩放
// 设置缩放的中心点位置
ViewCompat.setPivotX(mContentView,0);
ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2);
ViewCompat.setScaleX(mContentView,rightScale);
ViewCompat.setScaleY(mContentView, rightScale);
// 菜单的缩放和透明度
// 透明度是 半透明到完全透明 0.5f - 1.0f
float leftAlpha = 0.5f + (1-scale)*0.5f;
ViewCompat.setAlpha(mMenuView,leftAlpha);
// 缩放 0.7f - 1.0f
float leftScale = 0.7f + (1-scale)*0.3f;
ViewCompat.setScaleX(mMenuView,leftScale);
ViewCompat.setScaleY(mMenuView, leftScale);
// 最后一个效果 退出这个按钮刚开始是在右边,安装我们目前的方式永远都是在左边
// 设置平移,先看一个抽屉效果
// ViewCompat.setTranslationX(mMenuView,l);
// 平移 l*0.7f
ViewCompat.setTranslationX(mMenuView, 0.25f*l);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 2. 初始化进来是关闭
scrollTo(mMenuWidth, 0);
}
// 3.手指抬起是二选一,要么关闭要么打开
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 1. 获取手指滑动的速率,当期大于一定值就认为是快速滑动 , GestureDetector(系统提供好的类)
// 2. 处理事件拦截 + ViewGroup 事件分发的源码实践
// 当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件(打开情况下点击内容页不会响应点击事件)
if (ev.getAction() == MotionEvent.ACTION_UP) {
// 只需要管手指抬起 ,根据我们当前滚动的距离来判断
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
// 关闭
closeMenu();
} else {
// 打开
openMenu();
}
// 确保 super.onTouchEvent() 不会执行
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单 滚动到 0 的位置
*/
private void openMenu() {
// smoothScrollTo 有动画
smoothScrollTo(0, 0);
}
/**
* 关闭菜单 滚动到 mMenuWidth 的位置
*/
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0);
}
/**
* 获得屏幕高度
*
* @param context
* @return
*/
private int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
/**
* Dip into pixels
*/
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
最后的效果就是
// 1. 获取手指滑动的速率,当于一定值就认为是快速滑动 , GestureDetector(系统提供好的类)
// 2. 处理事件拦截 + ViewGroup 事件分发的源码实践
// 当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件(打开情况下点击内容页不会响应点击事件)