1.0版本
侧滑主页面,包含侧边菜单栏和主界面的消息栏显示
package com.example.qxb_810.wigdets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
/**
* create 2018/10/9
* desc 侧滑界面
*/
public class SlideMainView extends HorizontalScrollView {
private int mMenuWidth; // 侧滑菜单宽度
private int mScreenWidth; // 设备屏幕宽度
private boolean measureOnce = false;
private ViewGroup mMenu; // 侧滑菜单
private ViewGroup mContent; // 主显内容
public SlideMainView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
// 获取设备屏幕宽度
mScreenWidth = metrics.widthPixels;
}
// 测量控件尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!measureOnce) {
// 获取到对应控件
LinearLayout linearLayout = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) linearLayout.getChildAt(0);
mContent = (ViewGroup) linearLayout.getChildAt(1);
// 设置控件的宽高 -- 菜单设置宽度为屏幕百分之80, 主显区占满屏幕
mMenuWidth = mMenu.getLayoutParams().width = (int) (mScreenWidth * 0.8);
mContent.getLayoutParams().width = mScreenWidth;
measureOnce = true;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
super.onLayout(changed, l, t, r, b);
smoothScrollTo(mMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏,
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
int x = getScrollX();
// view左上角为原点 -- 大于mMenuWidth一半是则说明拖拽不到一半,回主显页
// 如果已经展开菜单且点击区域在主显区域,则将菜单收起, 或者拖拽出的侧边栏不到一半
// x=0,则说明菜单展开,ev.getX() > mMenuWidth说明点击区域在主显区域, getScrollX() > mMenuWidth / 2则说明拖拽出侧边栏不到一半
if ((x == 0 && ev.getX() > mMenuWidth) || getScrollX() > mMenuWidth / 2) {
mContent.setEnabled(true);
smoothScrollTo(mMenuWidth, 0);
} else {
mContent.setEnabled(false);
smoothScrollTo(0, 0);
}
return true; // return true 事件不发散
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
}
SlideItemView 编辑删除的消息栏控件
package com.example.qxb_810.wigdets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.example.qxb_810.qqslidingdemo.R;
/**
* create 2018/10/8
* desc 侧滑编辑删除View --- 练手用
*/
public class SlideItemView extends HorizontalScrollView {
private boolean once = false;
private int mScreenWidth; // 设备屏幕宽度
private int mItemWidth; // 模块宽度
private float mStartPoint = -1;
public SlideItemView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
mItemWidth = mScreenWidth / 2;
View editView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_edit, null);
View contentView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_content, null);
LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(mScreenWidth, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout rootView = new LinearLayout(context);
rootView.addView(contentView, contentParams);
rootView.addView(editView, editParams);
rootView.setOrientation(LinearLayout.HORIZONTAL);
this.addView(rootView, rootParams);
}
@Override // 防止父View拦截滑动事件
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (mStartPoint - ev.getX() > mItemWidth /2){
smoothScrollTo(mItemWidth, 0);
} else {
smoothScrollTo(0, 0);
}
return true;
} else if (ev.getAction() == MotionEvent.ACTION_DOWN){
mStartPoint = -1;
mStartPoint = ev.getX();
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
}
menu_content.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
<LinearLayout
android:id="@+id/ll_menu_head"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:background="@color/menuHead"
android:layout_margin="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="菜单头布局"
android:textSize="50dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_menu_item_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@color/menuItem"
android:layout_marginBottom="20dp"
android:layout_marginTop="20dp"
android:layout_margin="5dp">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:layout_margin="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="one"/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_menu_item_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@color/menuItem"
android:layout_margin="5dp">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:layout_margin="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="two"/>
</LinearLayout>
</LinearLayout>
main_show.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<com.example.qxb_810.wigdets.SlideItemView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/menuItem"
android:scrollbars="none">
</com.example.qxb_810.wigdets.SlideItemView>
</LinearLayout>
MainActivity
package com.example.qxb_810.qqslidingdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
主页面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.qxb_810.qqslidingdemo.MainActivity">
<com.example.qxb_810.wigdets.SlideMainView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include layout="@layout/menu_content" />
<include layout="@layout/main_show" />
</LinearLayout>
</com.example.qxb_810.wigdets.SlideMainView>
</LinearLayout>
2.0版本 – 基本完成了QQ滑动侧边栏以及滑动删除效果
不过这个这个版本本来没想写成这样,但是写着写着发现太复杂了,整体架构都偏出了我的想法。不过弄巧成拙,从这个极为混乱的Demo中学到了安卓事件分发。
总结几点关于安卓事件分发机制的内容:
1. getScrollX() 和getRawX()和onTouchEvent中的event.getX()区别:
- getScrollX – 相对于View的左上角,父布局(屏幕)左上角的坐标点,即View左上角为原点
- getRawX() 获得触摸点在整个屏幕的 X 轴坐标。在父View铺满屏幕时和getX()获取的值相同
- getX() – onTouchEvent 方法,其含义是相对于当前View左上角,你手指的坐标位置,即屏幕左上角为原点。
2. ViewGroup 和View的事件分发:详细内容可以参考https://www.cnblogs.com/linjzong/p/4191891.html
- ViewGroup中包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。
- 在ViewGroup中,当onInterceptTouchEvent返回True的时候事件会被自身处理,会调用ViewGroup的onTouchEvent事件,而dispatchTouchEvent返回true并不能能阻止继续调用onTouchEvent。
- ViewGroup 执行顺序 — dispatchTouchEvent --> onInterceptTouchEvent --> onTouchEvent
- 安卓点击事件传递, 首先由Activity分发,分发给根View,再分发给子View。执行顺序:onTouch > onLongClick > onClick, 前者会影响后者,后者不影响前者。如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发
- 将事件分发给子View需要两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept的true or false
// 将事件分发给子View需要一下两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept
if(disallowIntercept || !onInterceptTouchEvent(ev)){
将事件分发给子View
}
- 在dispatchTouchEvent() 中调用getParent().requestDisallowInterceptTouchEvent(true); 含义:当传入的参数为true时,表示子组件要自己消费这次事件,告诉父组件不要拦截(抢走)这次的事件。
- 当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
- 当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
- 当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
- onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
接下来项目截图:
GIF图失效,加几张图片吧
解释一下Demo开发:一开始只是想写个自定义滑动Demo — SlideMainView,类似版本1.0 后来写好之后觉着可以写一个类似QQ的滑动删除。随后增加一个侧滑编辑删除SlideItemView。本来想着将SlideItemView加入到SlideMainView中添加一些事件就行。但是后来发现由于事件分发导致很多事件不能触发。正常情况下,当时就觉着这个东西不能这么实现。但就是偏偏不信邪,于是便想着就要这么实现。在实现过程中也学习到了安卓的事件分发。与此同时,项目结构变得极其混乱,为了实现功能无所不用其极,最终诞生了这个Demo。
Demo结构: MainActivity中有一个自定义View — SlideMainView ,这个View其实包括侧边菜单和主显页面。
而主显页面中有一个RecyclerView,RecyclerView的itemView就是侧滑编辑删除菜单View — SlideItemView。
同时为了实现一些事件的触发,需要自定义一个RecyclerView — SlideRecyclerView。与此同时自定义一个帮助类 — MyLinearSnapHelper。
如图所示。。。。
MainActivity.java
package com.example.qxb_810.qqslidingdemo;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.example.qxb_810.wigdets.MyLinearSnapHelper;
import com.example.qxb_810.wigdets.SlideItemView;
import com.example.qxb_810.wigdets.SlideMainView;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.ll_menu_head)
LinearLayout llMenuHead;
@BindView(R.id.ll_menu_item_one)
LinearLayout llMenuItemOne;
@BindView(R.id.ll_menu_item_two)
LinearLayout llMenuItemTwo;
@BindView(R.id.rv_slide)
SlideRecyclerView rvSlide;
@BindView(R.id.smv_content)
SlideMainView smvContent;
private List<String> mItemlist = new ArrayList<>();
private SlideAdapter mSlideAdapter;
private int mCurrOpenPosition = -1; // 当前滑动打开编辑页的item position
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// 随便添加数据
mItemlist.add("0");
mItemlist.add("1");
mItemlist.add("2");
mItemlist.add("3");
mItemlist.add("4");
mItemlist.add("5");
mItemlist.add("6");
mItemlist.add("7");
mItemlist.add("8");
mItemlist.add("9");
mItemlist.add("10");
mItemlist.add("11");
mItemlist.add("12");
mItemlist.add("13");
mItemlist.add("14");
mItemlist.add("15");
mItemlist.add("16");
mItemlist.add("17");
mItemlist.add("18");
mItemlist.add("19");
mItemlist.add("20");
mSlideAdapter = new SlideAdapter(this, mItemlist);
rvSlide.addItemDecoration(new SpacesItemDecoration(10));
rvSlide.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
new MyLinearSnapHelper().attachToRecyclerView(rvSlide); // 使滑动RecyclerView结束时第一个item完整显示
rvSlide.setAdapter(mSlideAdapter);
initEvent();
}
/**
* 初始化事件
*/
private void initEvent() {
// 设置侧边栏点击事件
llMenuItemOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "llMenuItemOne", Toast.LENGTH_SHORT).show();
}
});
llMenuItemTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "llMenuItemTwo", Toast.LENGTH_SHORT).show();
}
});
// 添加RecyclerView Touch 事件, 用于刷新主显页
rvSlide.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mSlideAdapter.notifyDataSetChanged();
}
return false;
}
});
}
/**
* 消息栏适配器
*/
public class SlideAdapter extends RecyclerView.Adapter<SlideAdapter.SlideViewHolder> {
private Context context;
private List<String> list;
public SlideAdapter(Context context, List<String> list) {
this.context = context;
this.list = list;
}
@NonNull
@Override
public SlideViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.recyclerview_slide_item, parent, false);
return new SlideViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull SlideViewHolder holder, int position) {
// 将主显页的item全部设置为未滑开的的状态
holder.sivItem.setContent(list.get(position), null, null);
holder.sivItem.scrollTo(0, 0);
holder.sivItem.setOpen(false);
SlideItemView.isOneOpen = false;
mCurrOpenPosition = -1;
}
@Override
public int getItemCount() {
return list.size();
}
class SlideViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.siv_item)
SlideItemView sivItem;
public SlideViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
// 设置消息回调
sivItem.setmListenter(new SlideItemClickListener() {
@Override
public void onEdit() { // 点击编辑时
Toast.makeText(context, "onEdit", Toast.LENGTH_SHORT).show();
}
@Override
public void onDeltete() { // 点击删除时
notifyItemRemoved(getPosition());
list.remove(getPosition());
mCurrOpenPosition = -1;
Toast.makeText(context, "onDeltete", Toast.LENGTH_SHORT).show();
}
@Override
public void onContent() {
}
@Override
public void onMove() { // 当item滑动除编辑删除页的时候触发
// 记录当前打开的序号
mCurrOpenPosition = getPosition();
rvSlide.setmCurrOpenPosition(mCurrOpenPosition);
// Toast.makeText(context, "onMove + " + mCurrOpenPosition, Toast.LENGTH_SHORT).show();
}
@Override
public void onResume() { // 当item关闭编辑删除页时触发
mCurrOpenPosition = -1;
rvSlide.setmCurrOpenPosition(mCurrOpenPosition);
}
});
}
}
}
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
if (parent.getChildPosition(view) == 0)
outRect.top = space;
}
}
public interface SlideItemClickListener {
void onEdit(); // 点击编辑时调用
void onDeltete(); // 点击onDelete()时调用
void onContent(); // 点击onContent时调用
void onMove(); // 滑出编辑删除时调用
void onResume(); // item恢复时使用
}
}
MainActivity 布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.qxb_810.qqslidingdemo.MainActivity">
<com.example.qxb_810.wigdets.SlideMainView
android:id="@+id/smv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include layout="@layout/menu_content" />
<include layout="@layout/main_show" />
</LinearLayout>
</com.example.qxb_810.wigdets.SlideMainView>
</LinearLayout>
SlideMainView.java
package com.example.qxb_810.wigdets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.example.qxb_810.qqslidingdemo.MainActivity;
/**
* create 2018/10/9
* desc 侧滑界面 -- 这个界面包括侧滑的菜单页和主显内容页
*/
public class SlideMainView extends HorizontalScrollView {
private int mMenuWidth; // 侧滑菜单宽度
private int mScreenWidth; // 设备屏幕宽度
private boolean measureOnce = false;
private ViewGroup mMenu; // 侧滑菜单
private ViewGroup mContent; // 主显内容
private float mStartPoint = -1;
public boolean isMenuShow = false; // 菜单是否显示 --- 菜单是否被拖拽出来
public SlideMainView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
// 获取设备屏幕宽度
mScreenWidth = metrics.widthPixels;
}
@Override // ViewGroup onInterceptTouchEvent返回true,自己捕获事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 当菜单展开,且点击区域不在菜单区域时由自身处理该事件,调用自身onTouch
// isMenuShow 标识菜单展开, ev.getX() > mMenuWidth标识点击区域不是菜单区域,及点击的是部分显示的主显区域
// 此时调用本身的onTouchEvent事件
return isMenuShow && ev.getX() > mMenuWidth ? true : super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果没拖拽出菜单
if (!isMenuShow) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mStartPoint = -1;
mStartPoint = ev.getX();
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
// 判断如果是在拖拽主显区域的item,则将事件分发给子View
if (mStartPoint - ev.getX() > 0) { // 大于零时是往右拖拽,在菜单栏隐藏时往右拖拽则将事件分发给子View
requestDisallowInterceptTouchEvent(true); // true 则告诉View自己并不关心此次事件,分发给子View
}
}
}
return super.dispatchTouchEvent(ev);
}
// 测量控件尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!measureOnce) {
// 获取到对应控件
LinearLayout linearLayout = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) linearLayout.getChildAt(0);
mContent = (ViewGroup) linearLayout.getChildAt(1);
// 设置控件的宽高 -- 菜单设置宽度为屏幕百分之80, 主显区占满屏幕
mMenuWidth = mMenu.getLayoutParams().width = (int) (mScreenWidth * 0.8);
mContent.getLayoutParams().width = mScreenWidth;
measureOnce = true;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
super.onLayout(changed, l, t, r, b);
smoothScrollTo(mMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏,
isMenuShow = false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
// view左上角为原点 -- 大于mMenuWidth一半是则说明拖拽不到一半,回主显页
// 如果已经展开菜单且点击区域在主显区域,则将菜单收起, 或者拖拽出的侧边栏不到一半
// isOpen = false,则说明菜单展开,ev.getX() > mMenuWidth说明点击区域在主显区域, getScrollX() > mMenuWidth / 2则说明拖拽出侧边栏不到一半
if ((isMenuShow && ev.getX() > mMenuWidth) || getScrollX() > mMenuWidth / 2) {
smoothScrollTo(mMenuWidth, 0);
isMenuShow = false;
} else {
smoothScrollTo(0, 0);
isMenuShow = true;
}
return true; // return true 事件不发散
}
return super.onTouchEvent(ev);
}
}
SlideItemView
package com.example.qxb_810.wigdets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.qxb_810.qqslidingdemo.MainActivity;
import com.example.qxb_810.qqslidingdemo.R;
/**
* create 2018/10/8
* desc 侧滑编辑删除View --- 主显区域的item
*/
public class SlideItemView extends HorizontalScrollView implements View.OnClickListener {
private int mScreenWidth; // 设备屏幕宽度
private int mItemWidth; // 模块宽度
private float mStartPoint = -1;
private boolean isOpen = false;
public static boolean isOneOpen = false; // 用于标识RecyclerView中的item是否有滑出编辑删除页
private View mEditView;
private View mContentView;
private TextView mTvEdit; // 编辑tv
private TextView mTvDelete; // 删除tv
private TextView mTvContent; // content tv
private MainActivity.SlideItemClickListener mListenter;
public void setmListenter(MainActivity.SlideItemClickListener mListenter) {
this.mListenter = mListenter;
}
public void setOpen(boolean open) {
isOpen = open;
}
/**
* 设置属性值
*
* @param content content值
* @param edit edit值
* @param delete 删除值
*/
public void setContent(String content, String edit, String delete) {
if (content != null) {
mTvContent.setText(content);
}
if (edit != null) {
mTvEdit.setText(edit);
}
if (delete != null) {
mTvDelete.setText(delete);
}
}
public SlideItemView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
// 设置编辑删除区域的快读为屏幕宽度的一半
mItemWidth = mScreenWidth / 2;
// 动态添加编辑删除部分和content部分
mEditView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_edit, null);
mContentView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_content, null);
mTvEdit = mEditView.findViewById(R.id.tv_edit);
mTvDelete = mEditView.findViewById(R.id.tv_delete);
mTvContent = mContentView.findViewById(R.id.tv_content);
// 设置相应的点击事件
mTvEdit.setOnClickListener(this);
mTvDelete.setOnClickListener(this);
mTvContent.setOnClickListener(this);
// 动态添加view
LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(mScreenWidth, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout rootView = new LinearLayout(context);
rootView.addView(mContentView, contentParams);
rootView.addView(mEditView, editParams);
rootView.setOrientation(LinearLayout.HORIZONTAL);
this.addView(rootView, rootParams);
}
/*
将事件分发给子View需要一下两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept
if(disallowIntercept || !onInterceptTouchEvent(ev)){
将事件分发给子View
}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (isOpen) {
getParent().requestDisallowInterceptTouchEvent(true); // 设置父类的 disallowIntercept 为true,即父类不拦截
}
// disallowIntercept这个标志表示是否禁用事件拦截功能,默认情况都是false,当isOpen为false时,disallowIntercept为默认值false,
// 父View中super.onInterceptTouchEvent(ev)返回为false 则不将事件分发给子View当前View也收不到事件。
// 即isOpen为false时事件由父View处理。
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 检测拖拽,如果拖拽长度大于 编辑删除区域一半时则会默认拖拽出来,否则复位。
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (mStartPoint - ev.getX() > mItemWidth / 2) {
smoothScrollTo(mItemWidth, 0);
isOpen = true;
isOneOpen = true;
mListenter.onMove();
} else {
smoothScrollTo(0, 0);
isOpen = false;
isOneOpen = false;
mListenter.onResume();
}
return true;
} else if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 记录落点,这里会触发一次onContent回调,触发玩的,没什么用
mStartPoint = -1;
mStartPoint = ev.getX();
mListenter.onContent();
}
return super.onTouchEvent(ev);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_edit:
mListenter.onEdit();
break;
case R.id.tv_delete:
mListenter.onDeltete();
break;
case R.id.tv_content:
mListenter.onContent();
break;
default:
break;
}
}
}
SlideRecyclerView.java
package com.example.qxb_810.qqslidingdemo;
import android.content.Context;
import android.graphics.Matrix;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.example.qxb_810.wigdets.SlideItemView;
/**
* create 2018/10/12 16:36
* desc 自定义RecyclerView,解决分发事件问题
*/
public class SlideRecyclerView extends RecyclerView {
private int mCurrOpenPosition = -1;
private int mItemHeight = -1;
public void setmCurrOpenPosition(int mCurrOpenPosition) {
this.mCurrOpenPosition = mCurrOpenPosition;
}
public SlideRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// 计算出当前单击的item序号,判断点击的是否是当前滑开的item
LinearLayoutManager manager = ((LinearLayoutManager) getLayoutManager());
if (mItemHeight == -1) {
mItemHeight = manager.getChildAt(0).getHeight() + 10; // +10是item每个都设置了10像素的间距
}
int firstPosition = manager.findFirstVisibleItemPosition() + 1;
int position = (int) (e.getY() / mItemHeight) + firstPosition - 1;
// 如果有item被滑开且当前点击的item不是被滑开的item,则事件不会往下分发,由RecyclerView来处理该次事件 注: 会调用MainActivity中的RecyclerView设置的OnTouchListener事件
return SlideItemView.isOneOpen && mCurrOpenPosition != position ? true : super.onInterceptTouchEvent(e);
}
// @Override
// public void onScrollStateChanged(int state) {
// super.onScrollStateChanged(state);
// // OnScrollListener.SCROLL_STATE_FLING; //屏幕处于甩动状态
// // OnScrollListener.SCROLL_STATE_IDLE; //停止滑动状态
// // OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;// 手指接触状态
// if (state == SCROLL_STATE_IDLE) {
//
// // 整个滚动事件结束,只触发一次 顺序: 4
// Log.e("123", 3 + "");
// LinearLayoutManager manager = ((LinearLayoutManager) getLayoutManager());
//
// int firstPosition = manager.findFirstVisibleItemPosition();
// int completelyPosition = manager.findFirstCompletelyVisibleItemPosition();
// int lastPosition = manager.findFirstVisibleItemPosition();
//
// if (firstPosition != completelyPosition) {
// smoothScrollToPosition(firstPosition);
// }
// }
//
// }
}
recyclerview_slide_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<com.example.qxb_810.wigdets.SlideItemView
android:id="@+id/siv_item"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/menuItem"
android:scrollbars="none">
</com.example.qxb_810.wigdets.SlideItemView>
</LinearLayout>
sideslip_item_content.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:background="@color/menuHead">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="content"
android:textSize="50dp"
/>
</LinearLayout>
sideslip_item_edit.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_edit"
android:layout_width="80dp"
android:layout_height="match_parent"
android:textSize="30dp"
android:text="编辑"
android:background="@color/edit"
android:layout_weight="1"
android:gravity="center"
android:padding="10dp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="80dp"
android:layout_height="match_parent"
android:textSize="30dp"
android:background="@color/delete"
android:padding="10dp"
android:gravity="center"
android:layout_weight="1"
android:text="删除"/>
</LinearLayout>
MyLinearSnapHelper.java
这个类是拷贝大神的 。具体博客地址: https://blog.csdn.net/li15225271052/article/details/54747359
主要是用来在RecyclerView滑动结束时第一个item完整显示
package com.example.qxb_810.wigdets;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
public class MyLinearSnapHelper extends LinearSnapHelper {
private OrientationHelper mVerticalHelper, mHorizontalHelper;
public MyLinearSnapHelper() {
}
//这个是放入的被设置的recycleview'的
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
throws IllegalStateException {
super.attachToRecyclerView(recyclerView);
}
/**
* 计算到targetView要移动的距离 这个也是关键 这个方法是在我们判断处理之后(也就是findSnapView方法) 所以distanceToStart的方法中直接计算此时recycleview的离最屏幕开始处的距离减去内边距就是需要移动的距离
*/
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
//这里是个数组0返回的是横向的距离 1返回的是纵向的距离 默认的话你将每次都返回到第一个view
int[] out = new int[2];
//这里判断你设置的recycleview的布局管理器方向是横向还是纵向
if (layoutManager.canScrollHorizontally()) {
//然后将距离开始的距离存放在内
out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
//这里是获取开始显示的view方法
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof LinearLayoutManager) {
if (layoutManager.canScrollHorizontally()) {
return getStartView(layoutManager, getHorizontalHelper(layoutManager));
} else {
return getStartView(layoutManager, getVerticalHelper(layoutManager));
}
}
return super.findSnapView(layoutManager);
}
//这里是 计算距离开始的距离 由于calculateDistanceToFinalSnap的方法是在findSnapView之后所以这里计算的是recycleview的离最屏幕开始处的距离减去内边距就是需要移动的距离
private int distanceToStart(View targetView, OrientationHelper helper) {
Log.i("StartSnapHelper", "disanceToStart: "+helper.getDecoratedStart(targetView)+"========="+helper.getStartAfterPadding());
return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
}
//这里是我们自定义的右对齐的显示规则 也就是这里就是我实现自动最左对齐的关键 当然如果有其他的奇葩右对齐 -- (什么奇葩客户没见过)实现原理一样的
private View getStartView(RecyclerView.LayoutManager layoutManager,
OrientationHelper helper) {
//这里返回null表示不做任何操作保持当前的位置
if (layoutManager instanceof LinearLayoutManager) {
Log.i("StartSnapHelper", "getStartView: "+1);
int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
boolean isLastItem = ((LinearLayoutManager) layoutManager)
.findLastCompletelyVisibleItemPosition()
== layoutManager.getItemCount() - 1;
//当发现显示了最后一个item的时候不执行任何操作
if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
Log.i("StartSnapHelper", "getStartView: "+2);
return null;
}
View child = layoutManager.findViewByPosition(firstChild);
Log.i("StartSnapHelper11", "getStartView: "+helper.getDecoratedEnd(child)+ "------------"+helper.getDecoratedMeasurement(child));
//当发现当前第一个view最后到结束位置到屏幕距离大于item的一半的时候自动复位 或者用getDecoratedStart()判断条件反过来而已
if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
&& helper.getDecoratedEnd(child) > 0) {
Log.i("StartSnapHelper", "getStartView: "+3);
return child;
} else {
//如果发现最后停留的位置小于item自身的一半 则再次判断是不是最后一个view 如果是将不执行任何操作
if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
== layoutManager.getItemCount() - 1) {
Log.i("StartSnapHelper", "getStartView: "+4);
return null;
} else {
//如果不是返回下一个view
Log.i("StartSnapHelper", "getStartView: "+5);
return layoutManager.findViewByPosition(firstChild + 1);
}
}
}
return super.findSnapView(layoutManager);
}
private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
if (mVerticalHelper == null) {
mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
}
return mVerticalHelper;
}
private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
if (mHorizontalHelper == null) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return mHorizontalHelper;
}
}