效果图如下:
1.首页左侧drawerLaout
借鉴 https://github.com/qiantao94/CoordinatorMenu
小作修改:
因为该库不支持自定义侧边栏的宽度,我这边增加了一个属性drawerPercent,是指侧边栏占据手机屏幕的百分比。
2.item侧滑删除
借鉴 https://github.com/mcxtzhang/SwipeDelMenuLayout
注意:如果侧滑删除要和角标拖拽一起使用,直接依赖该库会出现滑动冲突。需要做如下修改:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogUtils.d(TAG, "dispatchTouchEvent() called with: " + "ev = [" + ev + "]"+isSwipeEnable);
if (isSwipeEnable) {
acquireVelocityTracker(ev);
final VelocityTracker verTracker = mVelocityTracker;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isUserSwiped = false;//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
isUnMoved = true;//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
iosInterceptFlag = false;//add by 2016 09 11 ,每次DOWN时,默认是不拦截的
if (isTouching) {//如果有别的指头摸过了,那么就return false。这样后续的move..等事件也不会再来找这个View了。
return false;
} else {
isTouching = true;//第一个摸的指头,赶紧改变标志,宣誓主权。
}
mLastP.set(ev.getRawX(), ev.getRawY());
mFirstP.set(ev.getRawX(), ev.getRawY());//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
//如果down,view和cacheview不一样,则立马让它还原。且把它置为null
if (mViewCache != null) {
LogUtil.d("4");
if (mViewCache != this) {
mViewCache.smoothClose();
iosInterceptFlag = isIos;//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。
}
//只要有一个侧滑菜单处于打开状态, 就不给外层布局上下滑动了
getParent().requestDisallowInterceptTouchEvent(true);
}
//求第一个触点的id, 此时可能有多个触点,但至少一个,计算滑动速率用
mPointerId = ev.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
if (iosInterceptFlag) {
break;
}
float gap = mLastP.x - ev.getRawX();
//为了在水平滑动中禁止父类ListView等再竖直滑动
if (Math.abs(gap) > 10 || Math.abs(getScrollX()) > 10) {//2016 09 29 修改此处,使屏蔽父布局滑动更加灵敏,
getParent().requestDisallowInterceptTouchEvent(true);
}
//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。begin
if (Math.abs(gap) > mScaleTouchSlop) {
isUnMoved = false;
}
//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。end
//如果scroller还没有滑动结束 停止滑动动画
/* if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}*/
scrollBy((int) (gap), 0);//滑动使用scrollBy
//越界修正
if (isLeftSwipe) {//左滑
if (getScrollX() < 0) {
scrollTo(0, 0);
}
if (getScrollX() > mRightMenuWidths) {
scrollTo(mRightMenuWidths, 0);
}
} else {//右滑
if (getScrollX() < -mRightMenuWidths) {
scrollTo(-mRightMenuWidths, 0);
}
if (getScrollX() > 0) {
scrollTo(0, 0);
}
}
mLastP.set(ev.getRawX(), ev.getRawY());
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
isUserSwiped = true;
}
//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
if (!iosInterceptFlag) {//且滑动了 才判断是否要收起、展开menu
//求伪瞬时速度
verTracker.computeCurrentVelocity(1000, mMaxVelocity);
final float velocityX = verTracker.getXVelocity(mPointerId);
if (Math.abs(velocityX) > 1000) {//滑动速度超过阈值
if (velocityX < -1000) {
if (isLeftSwipe) {//左滑
//平滑展开Menu
smoothExpand();
} else {
//平滑关闭Menu
smoothClose();
}
} else {
if (isLeftSwipe) {//左滑
// 平滑关闭Menu
smoothClose();
} else {
//平滑展开Menu
smoothExpand();
}
}
} else {
if (Math.abs(getScrollX()) > mLimit) {//否则就判断滑动距离
//平滑展开Menu
smoothExpand();
} else {
// 平滑关闭Menu
smoothClose();
}
}
}
releaseVelocityTracker();
isTouching = false;
break;
default:
break;
}
} else {
isTouching=false; //此处是处理冲突的位置,必须加
}
return super.dispatchTouchEvent(ev);
}
3.消息角标自由拖拽
借鉴:https://github.com/qstumn/BadgeView
如果开启拖拽和滑动删除一起使用,上面冲突要处理,下面代码也要写
badge.setOnDragStateChangedListener((dragState, badge1, targetView) -> {
//拖拽成功和拖拽取消后开启侧滑 其他状态关闭侧滑
//必须调用smoothClose()
if (dragState == Badge.OnDragStateChangedListener.STATE_SUCCEED) {
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);
} else if (dragState == Badge.OnDragStateChangedListener.STATE_CANCELED) {
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);
} else {
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(false).setSwipeEnable(false);
}
});
badge.setBadgeNumber(model.getCount());
4.可浮动的ImageView
@SuppressLint("AppCompatCustomView")
public class MoveImageView extends ImageView {
private Drawable mDrawable;
private int mLeft = 0;
private int mTop = 0;
private int mSpeed = 2;
private boolean isSetVerticalMove;
private boolean isMoveLeft;
private boolean isMoveUp;
private Handler mHandler;
private int mCanvasBgSize;
public MoveImageView(Context context) {
super(context);
}
public MoveImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setUp(context, attrs);
}
public MoveImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setUp(context, attrs);
}
private void setUp(Context context, AttributeSet attrs) {
TypedArray a =context.obtainStyledAttributes(attrs, R.styleable.MoveImage);
int direction=a.getInteger(R.styleable.MoveImage_direction,0);
mSpeed=a.getInteger(R.styleable.MoveImage_speed,2);
if (direction == 0) {
isSetVerticalMove = true;
} else {
isSetVerticalMove = false;
}
mDrawable = getDrawable();
mHandler = new MoveHandler();
mHandler.sendEmptyMessageDelayed(1, 220L);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isSetVerticalMove) {
canvas.translate(0.0F, mTop);
} else {
canvas.translate(mLeft, 0.0F);
}
mDrawable.draw(canvas);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (isSetVerticalMove) {
mCanvasBgSize = getMeasuredHeight() * 3 / 2;
mDrawable.setBounds(0, 0, getMeasuredWidth(), mCanvasBgSize);
} else {
mCanvasBgSize = getMeasuredWidth() * 3 / 2;
mDrawable.setBounds(0, 0, mCanvasBgSize, getMaxHeight());
}
}
private class MoveHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (isSetVerticalMove) {
if (isMoveUp) {
if (mTop <= getMeasuredHeight() - mCanvasBgSize)//此时表示移到了最up的位置
{
mTop += mSpeed;
isMoveUp = false;
} else//继续下移
{
mTop -= mSpeed;
}
} else {
if (mTop == 0)//此时表示移动到了最down,此时图片的up侧应该与屏幕up侧对齐,即坐标值为0
{
mTop -= mSpeed;
isMoveUp = true;//图片已经移动到了最down侧,需要修改其移动方向为up
} else {
mTop += mSpeed;//继续下移
}
}
} else {
if (isMoveLeft)//向左移动
{
if (mLeft <= getMeasuredWidth() - mCanvasBgSize)//此时表示移到了最左侧的位置
{
mLeft += mSpeed;
isMoveLeft = false;
} else//继续左移
{
mLeft -= mSpeed;
}
} else {
if (mLeft == 0)//此时表示移动到了最右侧,此时图片的左侧应该与屏幕左侧对齐,即坐标值为0
{
mLeft -= mSpeed;
isMoveLeft = true;//图片已经移动到了最右侧,需要修改其移动方向为向左
} else {
mLeft += mSpeed;//继续右移
}
}
}
invalidate();
mHandler.sendEmptyMessageDelayed(1, 22);
}
}
}
5.讨论组头像展示
借鉴 https://github.com/jinyb09017/MutiImgLoader
6.仿IOS弹性scrollview
/**
* created by dalang at 2018/11/21
* 仿IOS 弹性scrollview
*/
public class ReboundScrollView extends NestedScrollView {
//移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
//目的是达到一个延迟的效果
private static final float MOVE_FACTOR = 0.3f;
//松开手指后, 界面回到正常位置需要的动画时间
private static final int ANIM_TIME = 300;
//ScrollView的子View, 也是ScrollView的唯一一个子View
private View contentView;
//手指按下时的Y值, 用于在移动时计算移动距离
//如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
private float startY;
//用于记录正常的布局位置
private Rect originalRect = new Rect();
//手指按下时记录是否可以继续下拉
private boolean canPullDown = false;
//手指按下时记录是否可以继续上拉
private boolean canPullUp = false;
//在手指滑动的过程中记录是否移动了布局
private boolean isMoved = false;
public ReboundScrollView(Context context) {
super(context);
}
public ReboundScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
contentView = getChildAt(0);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(contentView == null) return;
//ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
.getRight(), contentView.getBottom());
}
/**
* 在触摸事件中, 处理上拉和下拉的逻辑
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (contentView == null) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//判断是否可以上拉和下拉
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
//记录按下时的Y值
startY = ev.getY();
break;
case MotionEvent.ACTION_UP:
if(!isMoved) break; //如果没有移动布局, 则跳过执行
// 开启动画
TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
originalRect.top);
anim.setDuration(ANIM_TIME);
contentView.startAnimation(anim);
// 设置回到正常的布局位置
contentView.layout(originalRect.left, originalRect.top,
originalRect.right, originalRect.bottom);
//将标志位设回false
canPullDown = false;
canPullUp = false;
isMoved = false;
break;
case MotionEvent.ACTION_MOVE:
//在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
if(!canPullDown && !canPullUp) {
startY = ev.getY();
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
break;
}
//计算手指移动的距离
float nowY = ev.getY();
int deltaY = (int) (nowY - startY);
//是否应该移动布局
boolean shouldMove =
(canPullDown && deltaY > 0) //可以下拉, 并且手指向下移动
|| (canPullUp && deltaY< 0) //可以上拉, 并且手指向上移动
|| (canPullUp && canPullDown); //既可以上拉也可以下拉
(这种情况出现在ScrollView包裹的控件比ScrollView还小)
if(shouldMove){
//计算偏移量
int offset = (int)(deltaY * MOVE_FACTOR);
//随着手指的移动而移动布局
contentView.layout(originalRect.left, originalRect.top + offset,
originalRect.right, originalRect.bottom + offset);
isMoved = true; //记录移动了布局
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 判断是否滚动到顶部
*/
private boolean isCanPullDown() {
return getScrollY() == 0 ||
contentView.getHeight() < getHeight() + getScrollY();
}
/**
* 判断是否滚动到底部
*/
private boolean isCanPullUp() {
return contentView.getHeight() <= getHeight() + getScrollY();
}
}
其他文章链接地址:
(一)高斯模糊实现毛玻璃效果丶共享元素动画 丶地址选择器
(二)仿京东顶部伸缩渐变丶自定义viewpager指示器丶viewpager3D回廊丶recyclerview瀑布流
(三)RxJava2常用操作符merge、flatmap、zip--结合MVP架构讲解
(四)仿支付宝首页顶部伸缩滑动/中间层下拉刷新
(五)TabLayout+ViewPager悬浮吸顶及刷新数量动画显示
(七)仿微信发布朋友圈拖拽删除
将持续更新.. 不喜勿喷,仅个人分享,希望能帮助到你
源码地址:Github传送门