Android开发侧滑效果的实现

在公司做开发的时候接到一个任务就是实现侧滑效果(酷狗比较老的版本的那种策划效果,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 事件分发的源码实践
//    当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件(打开情况下点击内容页不会响应点击事件)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值