仿QQ侧滑菜单
程序员文章站
2022-05-15 17:00:13
...
效果图
自定义控件
/**
* 自定义侧滑控件
* Created by xiaoyehai on 2016/11/29.
*/
public class SlidingMenu extends FrameLayout {
private View menuView; //菜单view
private View mainView; //主界面view
/**
* ViewDragHelper:它主要用于处理ViewGroup中对子View的拖拽处理,
* 本质是对触摸事件的解析类;
*/
private ViewDragHelper viewDragHelper;
private int width;
private float dragRange; //拖拽范围
// private FloatEvaluator floatEvaluator; //float计算器
// private IntEvaluator intEvaluator; //int计算器
//定义状态常量:枚举
enum DragState {
Open, Close;
}
//当前SlidingMenu的状态,默认关闭
private DragState currentState = DragState.Close;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* 获取SlidingMenu的状态
*
* @return
*/
public DragState getCurrentState() {
return currentState;
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, callback);
// floatEvaluator = new FloatEvaluator();
// intEvaluator = new IntEvaluator();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//简单的异常处理,该控件只能有2个布局
if (getChildCount() != 2) {
throw new IllegalArgumentException("SlidingMenu only have 2 children!");
}
menuView = getChildAt(0);
mainView = getChildAt(1);
}
/**
* 该方法在onMeasure()执行完后执行,可以在该方法初始化自己和子View的宽高
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getMeasuredWidth();
dragRange = width * 0.6f;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 让ViewDragHelper帮我们判断是否应该拦截
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将触摸事件交给ViewDragHelper来解析处理
viewDragHelper.processTouchEvent(event);
return true;
}
/**
* 回调类
*/
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
/**
* 用于判断是否捕获当前child的触摸事件
* @param child 当前触摸的子View
* @param pointerId
* @return true:捕获并解析 false:不处理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == menuView || child == mainView;
}
/**
* 获取view水平方向的拖拽范围
* @param child
* @return
*/
@Override
public int getViewHorizontalDragRange(View child) {
return (int) dragRange;
}
/**
* 控制child在水平方向的移动
* @param child
* @param left 当前child的即将移动到的位置,left=chile.getLeft()+dx
* @param dx 本次child水平方向移动的距离
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mainView) {
if (left < 0) { //限制mainView左边界
left = 0;
}
if (left > dragRange) { //限制mainView右边界
left = (int) dragRange;
}
} else if (child == menuView) { //menuView不可以滑动,设置left = left - dx;
//滑动menuView的时候让menuView不要移动,如果left = left - dx,这样做的话menuView无法移动,
//就不能让maiView伴随menuView移动
//left = left - dx;
}
return left;
}
/**
* 当child的位置改变的时候执行,一般用来做其他子View跟随该view移动
* @param changedView
* @param left
* @param top
* @param dx
* @param dy
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == menuView) {
//固定menuView,不让其移动
menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight());
int newLeft = mainView.getLeft() + dx;
if (newLeft < 0) {
newLeft = 0;
}
if (newLeft > dragRange) { //限制mainView右边界
newLeft = (int) dragRange;
}
//滑动menuView的时候让mainView跟着移动
mainView.layout(newLeft, mainView.getTop() + dy,
newLeft + mainView.getMeasuredWidth(), mainView.getBottom() + dy);
}
//计算mainView滑动的百分比
float fraction = mainView.getLeft() / dragRange;
//执行伴随动画
executeAnim(fraction);
//更改状态,接口回调
if (fraction == 0 && currentState != DragState.Close) {
//更改为关闭状态
currentState = DragState.Close;
if (listener != null) {
listener.onClose();
}
} else if (fraction == 1f && currentState != DragState.Open) {
//更改为打开状态
currentState = DragState.Open;
if (listener != null) {
listener.onOpen();
}
}
//将fraction暴露给外界
if (listener != null) {
listener.onDraging(fraction);
}
}
/**
* 手指抬起的执行该方法
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mainView.getLeft() < dragRange / 2) {
//在左半边,平滑动画
close();
} else {
//在右半边
open();
}
//处理用户的稍微滑动
if (xvel > 200 && currentState != DragState.Open) {
open();
} else if (xvel < -200 && currentState != DragState.Close) {
close();
}
}
};
/**
* 打开菜单
*/
public void open() {
viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange, mainView.getTop());
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}
/**
* 关闭菜单
*/
public void close() {
viewDragHelper.smoothSlideViewTo(mainView, 0, mainView.getTop());
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}
/**
* 执行伴随动画
*
* @param fraction
*/
private void executeAnim(float fraction) {
//缩小mainView
// mainView.setScaleX(1 - fraction * 0.2f);//0.8-1
//mainView.setScaleY(1 - fraction * 0.2f);
ViewHelper.setScaleX(mainView, 1 - fraction * 0.2f);
ViewHelper.setScaleY(mainView, 1 - fraction * 0.2f);
//menuView移动动画:-menuView.getMeasuredWidth() / 2 0
ViewHelper.setTranslationX(menuView, -menuView.getMeasuredWidth() / 2 + fraction * menuView.getMeasuredWidth() / 2);
//menuView放大动画
ViewHelper.setScaleX(menuView, 0.5f);
ViewHelper.setScaleY(menuView, 0.5f);
ViewHelper.setScaleX(menuView, 0.5f + 0.5f * fraction);
ViewHelper.setScaleY(menuView, 0.5f + 0.5f * fraction);
//menuView透明度动画
ViewHelper.setAlpha(menuView, 0.3f);
ViewHelper.setAlpha(menuView, 0.3f + 0.7f * fraction);
//给SlidingMenu的背景添加黑色的遮罩效果:menuView拉出来的时候背景慢慢变淡
getBackground().setColorFilter((Integer) ColorUtil.evaluateColor
(fraction, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
}
@Override
public void computeScroll() {
super.computeScroll();
if (viewDragHelper.continueSettling(true)) {
//如果动画没有结束,继续执行动画
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}
}
private onDragStateChangeListener listener;
public void setonDragStateChangeListener(onDragStateChangeListener listener) {
this.listener = listener;
}
//接口
public interface onDragStateChangeListener {
//打开的回调
void onOpen();
//关闭的回调
void onClose();
//正在拖拽中的回调
void onDraging(float fraction);
}
}
使用
/**
* QQ侧滑菜单
*/
public class MainActivity extends AppCompatActivity {
private ListView menu_listView, main_listview;
private SlidingMenu mSlidingMenu;
private ImageView ivHead;
private MyLinearLayout myLinearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
findViews();
init();
initListener();
}
private void init() {
menu_listView.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, Constant.sCheeseStrings) {
//重写getView(),可改变字体颜色
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(Color.WHITE);
return textView;
}
});
main_listview.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, Constant.NAMES) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
//先缩小view
ViewHelper.setScaleX(view, 0.5f);
ViewHelper.setScaleY(view, 0.5f);
//以属性动画放大
ViewPropertyAnimator.animate(view).scaleX(1f).setDuration(350).start();
ViewPropertyAnimator.animate(view).scaleY(1f).setDuration(350).start();
return view;
}
});
}
private void initListener() {
mSlidingMenu.setonDragStateChangeListener(new SlidingMenu.onDragStateChangeListener() {
@Override
public void onOpen() {
Log.i("info", "onOpen");
//打开的时候让menu_listView随机选中一个条目
menu_listView.smoothScrollToPosition(new Random().nextInt(menu_listView.getCount()));
}
@Override
public void onClose() {
Log.i("info", "onClose");
//关闭的时候让主界面左上角的图片抖一下
TranslateAnimation animation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0);
animation.setDuration(500);
animation.setInterpolator(new CycleInterpolator(4)); //设置循环移动次数
ivHead.startAnimation(animation);
}
@Override
public void onDraging(float fraction) {
Log.i("info", "onDraging" + fraction);
ivHead.setAlpha(1 - fraction);
}
});
//把SlidingMenu设置给myLinearLayout
myLinearLayout.setSlidingMenu(mSlidingMenu);
}
private void findViews() {
menu_listView = (ListView) findViewById(R.id.menu_listview);
main_listview = (ListView) findViewById(R.id.main_listview);
mSlidingMenu = (SlidingMenu) findViewById(R.id.slidingMenu);
ivHead = (ImageView) findViewById(R.id.iv_head);
myLinearLayout = (MyLinearLayout) findViewById(R.id.my_layout);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/slidingMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg">
<include layout="@layout/layout_menu" />
<include layout="@layout/layout_main" />
</com.xiaoyehai.slidingmenu.widget.SlidingMenu>
layout_menu.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="vertical"
android:paddingLeft="20dp"
android:paddingTop="50dp" >
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/head" />
<ListView
android:id="@+id/menu_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:listSelector="@android:color/transparent" >
</ListView>
</LinearLayout>
layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#459BFE">
<ImageView
android:id="@+id/iv_head"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:background="@drawable/head" />
</RelativeLayout>
<ListView
android:id="@+id/main_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@android:color/transparent"></ListView>
</com.xiaoyehai.slidingmenu.widget.MyLinearLayout>
当SlidingMenu打开的时候,拦截并消费触摸事件,让main_listview不能上下滑动
package com.xiaoyehai.slidingmenu.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
/**
* 当SlidingMenu打开的时候,拦截并消费触摸事件,让main_listview不能上下滑动
* Created by xiaoyehai on 2016/11/30.
*/
public class MyLinearLayout extends LinearLayout {
private SlidingMenu slidingMenu;
public void setSlidingMenu(SlidingMenu slidingMenu) {
this.slidingMenu = slidingMenu;
}
public MyLinearLayout(Context context) {
this(context, null);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
//当SlidingMenu打开的时候,拦截并消费触摸事件
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
//当SlidingMenu打开的时候,拦截并消费触摸事件
if (event.getAction() == MotionEvent.ACTION_UP) {
//点击mainview抬起时关闭菜单
slidingMenu.close();
}
return true;
}
return super.onTouchEvent(event);
}
}
ColorUtil
public class ColorUtil {
/**
* 根据百分比,从一个颜色到另一个颜色的渐变
*
* @param fraction 百分比0-1
* @param startValue 开始颜色
* @param endValue 结束颜色
* @return
*/
public static Object evaluateColor(float fraction, Object startValue,
Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
| (int) ((startB + (int) (fraction * (endB - startB))));
}
}
Constant
package com.xiaoyehai.slidingmenu;
public interface Constant {
public static final String[] sCheeseStrings = {
"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
"Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
"Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
"Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
"Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
"Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
"Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
"Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
};
public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用",
"公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
"武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
"雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
" 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
"魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
"郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
"李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
"陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
};
}
推荐阅读
-
学习使用Material Design控件(二)使用DrawerLayout实现侧滑菜单栏效果
-
Android侧滑菜单和轮播图之滑动冲突问题
-
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧滑动,右侧不动)
-
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧不动,右侧滑动)
-
android基于SwipeRefreshLayout实现类QQ的侧滑删除
-
Android解决viewpager嵌套滑动冲突并保留侧滑菜单功能
-
Vue 仿QQ左滑删除组件功能
-
Android仿微信联系人列表字母侧滑控件
-
Android自定义布局实现仿qq侧滑部分代码
-
Android实现3种侧滑效果(仿qq侧滑、抽屉侧滑、普通侧滑)