自定义view——仿酷狗的侧滑菜单
程序员文章站
2022-05-30 20:33:47
...
直接贴源码:注解内容里面都有
public class SlidingMenu extends HorizontalScrollView {
private final int mMenuWidth;
private View mMenuView;
private View mContentView;
//GestureDetector处理快速滑动
private GestureDetector mGestureDetector;
// 7.手指快速滑动 - 菜单是否打开
private boolean mMenuIsOpen = false;
private boolean mIsIntercept = false;
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 ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightMarign = ta.getDimension(R.styleable.SlidingMenu_menuRightMargin, dip2px(context, 50));
//菜单的宽度是屏幕的宽度-右边的一部分距离(自定义属性)
mMenuWidth = (int) (getScreenWidth(context) - rightMarign);
ta.recycle();
mGestureDetector = new GestureDetector(context, mGestureListener);
}
private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//快速滑动
Log.e("TAG", "velocityX—>" + velocityX);//向右滑动大于0,向左滑动小于0
// Bug 判断左右还是上下 只有左右快速滑动才切换
if(Math.abs(velocityY)>Math.abs(velocityX)){
return super.onFling(e1, e2, velocityX, velocityY);//默认返回的是false
}
if (mMenuIsOpen) {//菜单是打开的,且当向左滑动的时候关闭菜单
if (velocityX < 0) {
closeMenu();
return true;
}
} else {//菜单是关闭的,且当向右滑动的时候打开菜单
if (velocityX > 0) {
openMenu();
return true;
}
}
return super.onFling(e1, e2, velocityX, velocityY);//默认返回的是false
}
};
//1.宽度不对,指定宽高
@Override
protected void onFinishInflate() {
//这个方法是布局解析完毕
super.onFinishInflate();
//指定宽高
ViewGroup container = (ViewGroup) getChildAt(0);//LinearLayout
//1.内容的宽度是屏幕的宽度
int childCount = container.getChildCount();
if (childCount != 2) {
throw new RuntimeException("只能放置两个子View!");
}
mContentView = container.getChildAt(1);
ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
layoutParams.width = getScreenWidth(getContext());
mContentView.setLayoutParams(layoutParams);
//2.菜单的宽度是屏幕的宽度-右边的一部分距离(自定义属性)
mMenuView = container.getChildAt(0);
ViewGroup.LayoutParams menuParams = mMenuView.getLayoutParams();
menuParams.width = mMenuWidth;
mMenuView.setLayoutParams(menuParams);
}
//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;//变化是1->0
//右边的缩放 最小0.7f,最大1f
float rightScale = 0.7f + 0.3f * scale;
//设置右边的缩放,默认是中心点(ViewCompat兼容)
//设置中心点
ViewCompat.setPivotX(mContentView, 0);
ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2);
ViewCompat.setScaleX(mContentView, rightScale);
ViewCompat.setScaleY(mContentView, rightScale);
//左边缩放和透明度变化
//缩放是从0.7到1完全展开
float leftScale = 0.7f + (1 - scale) * 0.3f;
ViewCompat.setScaleX(mMenuView, leftScale);
ViewCompat.setScaleY(mMenuView, leftScale);
//透明度,半透明到完全透明
float leftAlpha = 0.5f + (1 - scale) * 0.5f;
ViewCompat.setAlpha(mMenuView, leftAlpha);
// 最后一个效果 退出这个按钮刚开始是在右边,安装我们目前的方式永远都是在左边
// 设置平移,先看一个抽屉效果
// ViewCompat.setTranslationX(mMenuView,l);
// 平移 l*0.25f
ViewCompat.setTranslationX(mMenuView, 0.25f * l);
}
//点击右边内容部分关闭菜单
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mIsIntercept = false;
if (mMenuIsOpen) {
float currentX = ev.getX();
if (currentX > mMenuWidth) {
//关闭菜单
closeMenu();
//消费当前事件,拦截子view的事件,但是此时会调用自己的onTouchEvent事件
mIsIntercept = true;
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
@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) {
if (mIsIntercept) {//当是拦截事件,消费当前事件
return true;
}
if (mGestureDetector.onTouchEvent(ev)) {//当点击了快速滑动,则消费当前事件
return true;
}
// 1. 获取手指滑动的速率,当期大于一定值就认为是快速滑动 , GestureDetector(系统提供好的类)
// 2. 处理事件拦截 + ViewGroup 事件分发的源码实践
// 当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,还需要拦截事件(打开情况下点击内容页不会响应点击事件)
if (ev.getAction() == MotionEvent.ACTION_UP) {
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
//关闭
closeMenu();
} else {
//打开
openMenu();
}
//确保不会调用super.onTouchEvent(ev),否则会不起作用
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打开菜单
*/
private void openMenu() {
//smoothScrollTo有动画
smoothScrollTo(0, 0);
mMenuIsOpen = true;
}
/**
* 关闭菜单
*/
private void closeMenu() {
//smoothScrollTo有动画
smoothScrollTo(mMenuWidth, 0);
mMenuIsOpen = false;
}
/**
* Dip into pixels
*/
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 获得屏幕宽度
*/
private int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
}
在上面的基础上实现QQ6.0侧滑效果
首先去掉缩放,其次添加阴影,缩放把之前代码直接注解掉就可以了
给内容布局添加阴影:首先将内容布局抠出来,然后套一个Relayout布局添加内容和阴影,然后再设置回去
//1.内容的宽度是屏幕的宽度
int childCount = container.getChildCount();
if (childCount != 2) {
throw new RuntimeException("只能放置两个子View!");
}
mContentView = container.getChildAt(1);
ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
//内容布局部分添加阴影
//把内容布局提取出来
container.removeView(mContentView);
//在外面添加一层阴影
RelativeLayout contentContainer = new RelativeLayout(getContext());
contentContainer.addView(mContentView);
mShadowView = new View(getContext());
mShadowView.setBackgroundColor(Color.parseColor("#55000000"));
contentContainer.addView(mShadowView);
//最后把容器放回原来的位置
layoutParams.width = getScreenWidth(getContext());
contentContainer.setLayoutParams(layoutParams);
container.addView(contentContainer);
mShadowView.setAlpha(0);
onScrollChanged方法中动态设置透明度
float scale = 1f * l / mMenuWidth;//变化是1->0
float alpha=1-scale;
mShadowView.setAlpha(alpha);
ViewCompat.setTranslationX(mMenuView, 0.6f * l);