(六)仿QQ首頁drawer/側滑刪除/浮動imgaeView/角標拖拽
效果圖如下:
1.首頁左側drawerLaout
借鑑 https://github.com/qiantao94/CoordinatorMenu
小作修改:
因為該庫不支援自定義側邊欄的寬度,我這邊增加了一個屬性drawerPercent,是指側邊欄佔據手機螢幕的百分比。
<com.dl.common.widget.drawerlayout.DrawerMenu
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:drawerPercent="0.8">
<include layout="@layout/demo6_layout_drawer_view" />
<include layout="@layout/demo6_layout_main_view" />
</com.dl.common.widget.drawerlayout.DrawerMenu>
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傳送門
相關文章
- Vue 仿QQ左滑刪除元件Vue元件
- 微信小程式仿微信, QQ 向左滑動刪除操作。微信小程式
- 側邊浮動網站客服QQ網站
- Android-WItemTouchHelperPlus幾行程式碼搞定仿QQ側滑Android行程
- Flutter | 超簡單仿微信QQ側滑選單元件Flutter元件
- wepy 滑動刪除功能
- Flutter:手把手教你實現一個仿QQ側滑選單的功能Flutter
- 如何快速刪除Word中的頁首橫線?刪除頁首橫線技巧分享
- ItemTouchHelper實現可拖拽和側滑的列表
- Latex角標 左側角標 左上角角標 左下角角標
- Flutter 仿iOS側滑返回案例實現FlutteriOS
- 原生js實現一個側滑刪除取消元件(item slide)JS元件IDE
- Flutter drawer側邊欄Flutter
- jQuery寫的文章內容頁右側浮動滾動jQuery
- 彈性效果網頁右側浮動框詳解網頁
- 自定義 behavior 完美仿 QQ 瀏覽器首頁,美團商家詳情頁瀏覽器
- 短視訊系統,長按側滑實現刪除的按鈕
- 懸浮球只在一側滑動 並且是橫屏狀態下
- vue 左滑刪除功能Vue
- Android側滑返回分析和實現(不高仿微信)Android
- 微信小程式-wepy-側滑刪除元件,支援自定義內容區在微信小程式元件
- 實現QQ的TabBar拖拽動效tabBar
- Vue實現浮動按鈕元件 - 頁面滾動時自動隱藏 - 可拖拽Vue元件
- JavaScript物件導向怎樣刪除標籤頁?JavaScript物件
- Flutter自定義View——仿高德三級聯動DrawerFlutterView
- flutter TabBarView 動態新增刪除頁面FluttertabBarView
- 給你的頁面帶上側滑返回——SlideBackIDE
- html裡列表滑動刪除的實現如此簡單HTML
- vue移動端側滑皮膚元件Vue元件
- win10 QQ熱點資訊怎樣刪除_win10刪除QQ熱點資訊教程Win10
- 仿 “即刻APP” 滑動返回的效果APP
- iOS 如何絲滑的側滑返回iOS
- 清浮動的六種方式
- mui關閉側滑UI
- Qt 過載QComboBox,實現右側刪除鍵QT
- RecyclerView 梳理:點選&長按事件、分割線、拖曳排序、滑動刪除View事件排序
- php短視訊原始碼,向左滑動顯示刪除按鈕PHP原始碼
- 微信小程式swiper實現 句子控app首頁滑動卡片微信小程式APP