Android FrameLayout+ViewDragHelper实现QQ7.1.0侧滑菜单

大家好!这篇介绍FrameLayout+ViewDragHelper实现QQ7.1.0侧滑菜单,在QQ侧滑菜单上我而外添加了主页面左侧阴影效果(希望出现立体效果),实际上功能很简单,如果我有没有写好的地方,欢迎大家多多意见哈,如果你有任何想法都可以提出来探讨。
其他先不说,我们先上最后的效果图,我效果图录制的不好,后面我争取录制更好的,大家不要介意哈:

GIF.gif

1、背景及其基础知识

a、FrameLayout(帧布局)

帧布局很简单布局,属于Android六大布局之一,它里面所有的一级控件都是叠加放在左上角的,没有相关位置属性。其他有关帧布局的详情看大神们写的Android布局详解之一:FrameLayout,在这里我就不多说了哈,我们主要用到的是自定义帧布局,也就是继承FrameLayout布局。

b、ViewDragHelper

官方在v4的支持包中,提供了ViewDragHelper这样一个类帮助我们方便的编写自定义ViewGroup,非常强大和好用的类,可以扩展很多特效出来,下拉、侧滑等等,官方注解:

/**
 * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
 * of useful operations and state tracking for allowing a user to drag and reposition
 * views within their parent ViewGroup.
 */

用法
第一步,声明

mDragger = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());

第一个参数当前ViewGroup,第二个参数主要是控制灵敏度,主要设置触动速度,第三个参数最重要,许多操作都在里面,继承ViewDragHelper.Callback抽象类,自己实现里面需要实现的方法
第二步,拦截onInterceptTouchEvent交给ViewDragHelper处理,onTouchEvent也需要传给它

 /**
     * 拦截触摸事件
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragger.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragger.processTouchEvent(event);
        return true;
    }

第三步,实现ViewDragHelper.Callback抽象类里相关的方法,简单介绍下其中重要的一些方法的意思:

//进行捕获拦截,那些View可以进行drag操作
//传递当前触摸的子View实例,如果当前的子View需要进行拖拽移动返回true
tryCaptureView(View child, int pointerId)
//控制水平方向移动的范围
clampViewPositionHorizontal(View child, int left, int dx)
//控制垂直方向移动的范围
clampViewPositionVertical(View child, int top, int dy)
//返回可拖动的子视图的垂直移动范围的大小。 这个方法应该返回0,因为视图不能垂直移动。
getViewVerticalDragRange(View child)
//返回可拖动的子视图的水平移动范围的大小。 这个方法应该返回0,因为视图不能水平移动。
getViewHorizontalDragRange(View child)
//当捕获视图的位置发生变化时调用。
onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
//调用子视图不再被积极地拖拽。如果相关的话,还提供了抛掷速度
onViewReleased(View releasedChild, float xvel, float yvel)
//当父视图中的一个订阅边被触摸时调用 ,由用户而不是子视图当前捕获。
//注意,必须开启边界使用的模式:dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
onEdgeTouched(int edgeFlags, int pointerId)
//当人为的从边界拖动(此时并没有拖动子view)时,可以选择关联某一个子view,实现只要从边界拖动,不管是否触碰到子view,都能控制子view一起拖动的效果
//注意同上个方法,必须要开启才可以使用
onEdgeDragStarted(int edgeFlags, int pointerId)

2、编写的步骤和思路(我代码里有非常详细的注释,相信基本都能看懂)

a、基础类创建

创建自定义VDHLayout继承FrameLayout,实现里面主要方法:

private Context context;
//定义一个ViewDragHelper
public ViewDragHelper mDragger;

public VDHLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        mDragger = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
    }

b、创建布局(***-这个是表示必须需要的布局)

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoqiang.qqmenu.view.VDHLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/vDHLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <!-- ***侧滑页面布局 -->
    <RelativeLayout
        android:id="@+id/left"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginRight="100dp"
        android:background="@android:color/white">
        <!-- 测试数据 -->
        <ImageView
            android:id="@+id/left_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/ic_launcher" />
        <!-- 测试列表数据 -->
        <ListView
            android:id="@+id/left_list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/left_image"></ListView>

    </RelativeLayout>
    <!-- 主页面左侧阴影(可以选择有或者无,如果无的话就去VDHLayout里删除相关代码) -->
    <LinearLayout
        android:layout_width="8dp"
        android:layout_height="match_parent"
        android:background="@drawable/left_shade"></LinearLayout>
    <!-- ***主页面布局 -->
    <RelativeLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:visibility="visible">
        <!-- 测试数据 -->
        <ImageView
            android:id="@+id/main_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/ic_launcher" />

        <!-- 测试列表数据 -->
        <ListView
            android:id="@+id/main_list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/main_image"></ListView>
        <!-- ***打开侧边页面主页面实现逐渐变暗 -->
        <ImageView
            android:id="@+id/main_top_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#000000" />

    </RelativeLayout>

</com.xiaoqiang.qqmenu.view.VDHLayout>

c、申明要使用的变量和初始化数据

//主页面
    private RelativeLayout mainView;
    //左侧菜单
    private RelativeLayout leftView;
    //主页面左边阴影
    private LinearLayout left_shade;
    //左侧菜单宽高
    private int width, height;
    //主页面宽度,屏幕宽度
    private int mainWidth;
    //水平拖拽的距离
    private int range;
    //自定义监听事件
    private VDHLayoutListener vdhLayoutListener;
    //页面状态 默认为关闭
    private Status status = Status.CLOSE;
    //主页面close时,逐渐变暗
    private ImageView main_top_bg;
    //滑动进度
    private float percent;

/**
     * 调用进行left和main 视图进行初始位置布局
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        leftView.layout(-(width / 2), 0, width / 2, height);
        mainView.layout(0, 0, mainWidth, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = leftView.getMeasuredWidth();
        height = leftView.getMeasuredHeight();
        mainWidth = getWidth();
//        width = (int)(mainWidth*0.5);
    }

    /**
     * 当View中所有的子控件均被映射成xml后触发
     * <p>
     * 布局加载完成回调
     * 做一些初始化的操作
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        leftView = (RelativeLayout) getChildAt(0);
        left_shade = (LinearLayout) getChildAt(1);
        mainView = (RelativeLayout) getChildAt(2);
        main_top_bg = (ImageView) mainView.findViewById(R.id.main_top_bg);
        main_top_bg.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                close();
            }
        });
        main_top_bg.setAlpha(0f);
        main_top_bg.setVisibility(GONE);
    }

d、限制侧边布局滑动范围(通过ViewDragHelper.Callback限制)

首先进行哪些View可以移动的捕获

       /**
         * 进行捕获拦截,那些View可以进行drag操作
         * 传递当前触摸的子View实例,如果当前的子View需要进行拖拽移动返回true
         * @param child
         * @param pointerId
         * @return 直接返回true,拦截所有的VIEW
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

限制View只能水平移动

       /**
         * 决定拖拽的View在垂直方向上面移动到的位置
         * @param child
         * @param top
         * @param dy
         * @return
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            top = 0;
            return top;
        }

限制View水平上移动的范围,水平上移动的范围不能超过侧边页面的宽度width

       /**
         * 决定拖拽的View在水平方向上面移动到的位置
         * @param child
         * @param left
         * @param dx
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == mainView) {//主页面按住滑动
            }
            if (child == leftView) {//侧边页面按住滑动
                left = mainView.getLeft() + dx;
            }
            if (left < 0) {
                left = 0;
            } else if (left > width) {
                left = width;
            }
            return left;
        }

e、侧边和主页面滑动控制(通过ViewDragHelper.Callback控制),这是最重要的

我们可以从QQ上操作看到,侧边页面是两边往中间收缩,速度只有主页面的一般
代码如下:

       /**
         * 当前被触摸的View位置变化时回调
         * changedView为位置变化的View,left/top变化时新的x左/y顶坐标,dx/dy为从旧到新的偏移量
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //设置主页面位置
            leftView.layout(left / 2 - width / 2, 0, width / 2 + left / 2, height);
            //设置侧边页面位置
            mainView.layout(left, 0, mainWidth + left, height);
            //设置主页面左边阴影位子
            left_shade.layout(left - left_shade.getMeasuredWidth(), 0, left, height);
            //记录移动位置
            range = left;
            //滑动时主页面上明暗变化
            percent = range / (float) width;
            main_top_bg.setAlpha(percent / 2f);
            if (percent == 0) {
                main_top_bg.setVisibility(View.GONE);
            } else {
                main_top_bg.setVisibility(View.VISIBLE);
            }
            //滑动进度返回
            if (vdhLayoutListener != null) {
                vdhLayoutListener.onDrag(percent);
            }
        }

f、当手指松开,页面自动判断位置,进行关闭或者打开侧滑

       /**
         * 当拖拽的子View,手势释放的时候回调的方法, 然后根据左滑或者右滑的距离进行判断打开或者关闭
         * @param releasedChild
         * @param xvel
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mainView && range > width / 2) {
                open();
            } else if (releasedChild == leftView && range < width / 2) {
                close();
            } else if (releasedChild == mainView) {
                close();
            } else {
                open();
            }
        }

   /**
     * 关闭侧边菜单
     */
    public void close() {
        //继续滑动
        if (mDragger.smoothSlideViewTo(mainView, 0, 0)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        if (vdhLayoutListener != null) {
            vdhLayoutListener.close();
        }
    }

   /**
     * 打开侧边菜单
     */
    public void open() {
        if (mDragger.smoothSlideViewTo(mainView, width, 0)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        if (vdhLayoutListener != null) {
            vdhLayoutListener.open();
        }
    }

3、主要代码的源码

package com.xiaoqiang.qqmenu.view;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import com.xiaoqiang.qqmenu.R;

/** 
 * description: 防QQ 7.1.0侧滑菜单
 * autour: xiaoqiang
 * mail:18767164694@126.com
 * qq:773860458
 * date: 2017/6/16 14:28
*/
public class VDHLayout extends FrameLayout {

    private Context context;
    //定义一个ViewDragHelper
    public ViewDragHelper mDragger;
    //主页面
    private RelativeLayout mainView;
    //左侧菜单
    private RelativeLayout leftView;
    //主页面左边阴影
    private LinearLayout left_shade;
    //左侧菜单宽高
    private int width, height;
    //主页面宽度,屏幕宽度
    private int mainWidth;
    //水平拖拽的距离
    private int range;
    //自定义监听事件
    private VDHLayoutListener vdhLayoutListener;
    //页面状态 默认为关闭
    private Status status = Status.CLOSE;
    //主页面close时,逐渐变暗
    private ImageView main_top_bg;
    //滑动进度
    private float percent;

    public VDHLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        mDragger = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
    }

    class DragHelperCallback extends ViewDragHelper.Callback {
        /**
         * 进行捕获拦截,那些View可以进行drag操作
         * 传递当前触摸的子View实例,如果当前的子View需要进行拖拽移动返回true
         * @param child
         * @param pointerId
         * @return 直接返回true,拦截所有的VIEW
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        /**
         * 决定拖拽的View在水平方向上面移动到的位置
         * @param child
         * @param left
         * @param dx
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == mainView) {//主页面按住滑动
            }
            if (child == leftView) {//侧边页面按住滑动
                left = mainView.getLeft() + dx;
            }
            if (left < 0) {
                left = 0;
            } else if (left > width) {
                left = width;
            }
            return left;
        }

        /**
         * 决定拖拽的View在垂直方向上面移动到的位置
         * @param child
         * @param top
         * @param dy
         * @return
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            top = 0;
            return top;
        }


        @Override
        public int getViewHorizontalDragRange(View child) {
            return 1;
        }

        /**
         * 当前被触摸的View位置变化时回调
         * changedView为位置变化的View,left/top变化时新的x左/y顶坐标,dx/dy为从旧到新的偏移量
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //设置主页面位置
            leftView.layout(left / 2 - width / 2, 0, width / 2 + left / 2, height);
            //设置侧边页面位置
            mainView.layout(left, 0, mainWidth + left, height);
            //设置主页面左边阴影位子
            left_shade.layout(left - left_shade.getMeasuredWidth(), 0, left, height);
            //记录移动位置
            range = left;
            //滑动时主页面上明暗变化
            percent = range / (float) width;
            main_top_bg.setAlpha(percent / 2f);
            if (percent == 0) {
                main_top_bg.setVisibility(View.GONE);
            } else {
                main_top_bg.setVisibility(View.VISIBLE);
            }
            //滑动进度返回
            if (vdhLayoutListener != null) {
                vdhLayoutListener.onDrag(percent);
            }
        }

        /**
         * 当拖拽的子View,手势释放的时候回调的方法, 然后根据左滑或者右滑的距离进行判断打开或者关闭
         * @param releasedChild
         * @param xvel
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mainView && range > width / 2) {
                open();
            } else if (releasedChild == leftView && range < width / 2) {
                close();
            } else if (releasedChild == mainView) {
                close();
            } else {
                open();
            }
        }
    }

    /**
     * 拦截触摸事件
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragger.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragger.processTouchEvent(event);
        return true;
    }

    /**
     * 调用进行left和main 视图进行初始位置布局
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        leftView.layout(-(width / 2), 0, width / 2, height);
        mainView.layout(0, 0, mainWidth, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = leftView.getMeasuredWidth();
        height = leftView.getMeasuredHeight();
        mainWidth = getWidth();
//        width = (int)(mainWidth*0.5);
    }

    /**
     * 当View中所有的子控件均被映射成xml后触发
     * <p>
     * 布局加载完成回调
     * 做一些初始化的操作
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        leftView = (RelativeLayout) getChildAt(0);
        left_shade = (LinearLayout) getChildAt(1);
        mainView = (RelativeLayout) getChildAt(2);
        main_top_bg = (ImageView) mainView.findViewById(R.id.main_top_bg);
        main_top_bg.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                close();
            }
        });
        main_top_bg.setAlpha(0f);
        main_top_bg.setVisibility(GONE);
    }

    /**
     * 有加速度,当我们停止滑动的时候,该不会立即停止动画效果
     */
    @Override
    public void computeScroll() {
        if (mDragger.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * 页面状态(滑动,打开,关闭)
     */
    public enum Status {
        DRAG, OPEN, CLOSE
    }

    /**
     * 页面状态设置
     *
     * @return
     */
    public Status getStatus() {
        if (range == 0) {
            status = Status.CLOSE;
        } else if (range == width) {
            status = Status.OPEN;
        } else {
            status = Status.DRAG;
        }
        return status;
    }

    /**
     * 关闭侧边菜单
     */
    public void close() {
        //继续滑动
        if (mDragger.smoothSlideViewTo(mainView, 0, 0)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        if (vdhLayoutListener != null) {
            vdhLayoutListener.close();
        }
    }

    /**
     * 打开侧边菜单
     */
    public void open() {
        if (mDragger.smoothSlideViewTo(mainView, width, 0)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        if (vdhLayoutListener != null) {
            vdhLayoutListener.open();
        }
    }

    public interface VDHLayoutListener {
        //打开侧边页面
        void open();
        //关闭侧边页面
        void close();
        //打开关闭侧边页面进度返回,0——关闭,1——打开
        void onDrag(float percent);
    }

    public void setVdhLayoutListener(VDHLayoutListener vdhLayoutListener) {
        this.vdhLayoutListener = vdhLayoutListener;
    }
}

4、工程代码下载

源码下载

谢谢大家的观看,谢谢大家的支持,谢谢大家的喜欢,谢谢大家的关注,每篇文章,我都会尽我最大的力量编写,谢谢!分享一些好的技术,Kotlin技术最近也逐渐火起来了,后续我也会增加Kotlin分享,也许以后Android都用Kotlin进行开发。
下一期将发表个小的效果,用Toast实现一个顶部自定义通知,欢迎大家关注,提意见,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容