效果圖如下:
實現(xiàn)過程:
1.先從布局入手匣缘。要實現(xiàn)recyclerview全屏的拖拽蚓土,布局思路一定要正確掺出。布局關(guān)系如下:
布局有一點需要注意:
recyclerview高度必須match_parent忆首,這樣才能全屏拖拽忠藤,但是為什么又要位于 editText之下呢阅爽。
這樣是為了防止editText焦點被奪取路幸,無法輸入。位于editText之下后recyclerview又怎么滑出自己的布局呢付翁,這時需要一個屬性設(shè)置clipChildren=false.允許子View超出父View劝赔。
2.拖拽功能實現(xiàn)。 利用ItemTouchHelper
(1)自定義一個類集成并實現(xiàn)ItemTouchHelper.Callback(功能核心,代碼里有注釋)
/**
* created by dalang at 2018/11/26
* 微信拖拽排序刪除
*/
public class WXTouchHelper extends ItemTouchHelper.Callback {
private int dragFlags;
private int swipeFlags;
private BGARecyclerViewAdapter adapter;
private List<String> imagesList;//圖片的順序與拖拽順序保持一致
private boolean up;//手指抬起標(biāo)記位
private NestedScrollView scrollView;
public WXTouchHelper(BGARecyclerViewAdapter adapter, List<String> imagesList, NestedScrollView scrollView) {
this.adapter = adapter;
this.imagesList = imagesList;
this.scrollView=scrollView;
}
/**
* 設(shè)置item是否處理拖拽事件和滑動事件胆敞,以及拖拽和滑動操作的方向
*
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//判斷 recyclerView的布局管理器數(shù)據(jù)
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {//設(shè)置能拖拽的方向
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
swipeFlags = 0;//0則不響應(yīng)事件
}
return makeMovementFlags(dragFlags, swipeFlags);
}
/**
* 當(dāng)用戶從item原來的位置拖動可以拖動的item到新位置的過程中調(diào)用
*
* @param recyclerView
* @param viewHolder
* @param target
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder
viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//得到item原來的position
int toPosition = target.getAdapterPosition();//得到目標(biāo)position
//因為沒有將 +號的圖片 加入imageList,所以不用imageList.size-1 此處限制不能移動到recyclerView最后一位
if (toPosition == imagesList.size() || imagesList.size() == fromPosition) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(imagesList, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(imagesList, i, i - 1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
/**
* 設(shè)置是否支持長按拖拽
* 此處必須返回false
* 需要在recyclerView長按事件里限制,否則最后+號長按后扔可拖拽
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return false;
}
/**
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
/**
* 當(dāng)用戶與item的交互結(jié)束并且item也完成了動畫時調(diào)用
*
* @param recyclerView
* @param viewHolder
*/
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
adapter.notifyDataSetChanged();
initData();
if (dragListener != null) {
dragListener.clearView();
}
}
/**
* 重置
*/
private void initData() {
if (dragListener != null) {
dragListener.deleteState(false);
dragListener.dragState(false);
}
up = false;
}
/**
* 自定義拖動與滑動交互
*
* @param c
* @param recyclerView
* @param viewHolder
* @param dX
* @param dY
* @param actionState
* @param isCurrentlyActive
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (null == dragListener) {
return;
}
//recyclerview上面的editText的高度為100
int editTextHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_100);
//刪除按鈕高度
int buttonHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_50);
/**
* item間隔10dp,因為item的xml布局中有個10dp的空白着帽,
* 拖拽時是拖拽整個itemView所有導(dǎo)致底部10dp空白先接觸刪除按鈕所以在判斷閾值時應(yīng)該考慮此10dp,
* 如果是采用addItemDecoration添加分割線就不用考慮這10dp杂伟,但是拖拽時會出現(xiàn)分割線遮擋的情況,具體效果可以自己實驗一下
*/
int spaceHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_10);//
/**
* scrollView.getHeight()-editTextHeight 為recyclerview的高度
* 此處不用onChildDraw里的參數(shù)recyclerView.getHeight來計算,因為當(dāng)添加圖片至超出屏幕高度
* 即scrollView可以滑動后獲取的recyclerview不準(zhǔn)確,親測仍翰。
*/
if (dY>=(scrollView.getHeight()-editTextHeight)
- viewHolder.itemView.getBottom()//item底部距離recyclerView頂部高度
-buttonHeight
+scrollView.getScrollY()
+spaceHeight) {//拖到刪除處
dragListener.deleteState(true);
if (up) {//在刪除處放手赫粥,則刪除item
//先設(shè)置不可見,如果不設(shè)置的話予借,會看到viewHolder返回到原位置時才消失
//越平,因為remove會在viewHolder動畫執(zhí)行完成后才將viewHolder刪除
viewHolder.itemView.setVisibility(View.INVISIBLE);
imagesList.remove(viewHolder.getAdapterPosition());
dragListener.deleteOk();
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
initData();
return;
}
} else {//沒有到刪除處
//如果viewHolder不可見,則表示用戶放手灵迫,重置刪除區(qū)域狀態(tài)
if (View.INVISIBLE == viewHolder.itemView.getVisibility()) {
dragListener.dragState(false);
}
dragListener.deleteState(false);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
/**
* 當(dāng)長按選中item的時候(拖拽開始的時候)調(diào)用
*
* @param viewHolder
* @param actionState
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) {
dragListener.dragState(true);
}
super.onSelectedChanged(viewHolder, actionState);
}
/**
* 設(shè)置手指離開后ViewHolder的動畫時間秦叛,在用戶手指離開后調(diào)用
*
* @param recyclerView
* @param animationType
* @param animateDx
* @param animateDy
* @return
*/
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType,
float animateDx, float animateDy) {
//手指放開
up = true;
return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
}
public interface DragListener {
/**
* 用戶是否將 item拖動到刪除處,根據(jù)狀態(tài)改變顏色
*
* @param delete
*/
void deleteState(boolean delete);
/**
* 是否于拖拽狀態(tài)
*
* @param start
*/
void dragState(boolean start);
/**
* 當(dāng)用戶與item的交互結(jié)束并且item也完成了動畫時調(diào)用
*/
void clearView();
/**
* 當(dāng)刪除完成后調(diào)用
*/
void deleteOk();
}
private DragListener dragListener;
public void setDragListener(DragListener dragListener) {
this.dragListener = dragListener;
}
(2)activity調(diào)用及處理
拖拽事件開啟
//綁定recyclerview
WXTouchHelper myCallBack = new WXTouchHelper(photoPublishAdapter, imgSelected,scrollView);
itemTouchHelper = new ItemTouchHelper(myCallBack);
itemTouchHelper.attachToRecyclerView(recyclerPhoto);
recyclerPhoto.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerPhoto) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getAdapterPosition() == imgSelected.size()) {
DialogUtil.uploadMultiplePhoto(mActivity, getTakePhoto(), limit);
} else {
if (imgSelected.size() != 0) {
Intent intent = new Intent(mActivity, BigPhotoActivity.class);
intent.putStringArrayListExtra("imgUrls", (ArrayList<String>) imgSelected);
intent.putExtra("position", viewHolder.getAdapterPosition());
mSwipeBackHelper.forward(intent);
}
}
}
//長按事件中開啟拖拽 需要判斷position不是+號圖片
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getAdapterPosition() != imgSelected.size()) {
BGAKeyboardUtil.closeKeyboard(mActivity);
itemTouchHelper.startDrag(viewHolder);
}
}
});
刪除按鈕顯示
myCallBack.setDragListener(new WXTouchHelper.DragListener() {
@Override
public void deleteState(boolean delete) {
if (delete) {
tvDelete.setAlpha(0.8f);
tvDelete.setText("松手即可刪除");
} else {
tvDelete.setAlpha(0.5f);
tvDelete.setText("拖到此處刪除");
}
}
@Override
public void dragState(boolean start) {
if (start) {
tvDelete.setVisibility(View.VISIBLE);
} else {
tvDelete.setVisibility(View.GONE);
}
}
@Override
public void clearView() {
//刪除圖片后需要重新計算recyclerview下面布局的margin
fixBottom();
}
@Override
public void deleteOk() {
//刪除后重新計算圖片選擇數(shù)量
limit = 9 - imgSelected.size();
}
});
底部布局處理
/**
* 處理recyclerView下面的布局
*/
private void fixBottom() {
int row = photoPublishAdapter.getItemCount() / 3;
row = (0 == photoPublishAdapter.getItemCount() % 3) ? row : row + 1;//少于3為1行
row = (4 == row) ? 3 : row;//最多為三行
int width = DisplayUtil.getScreenWidth(mActivity);
int itemWidth = (int) (width - getResources().getDimension(R.dimen.dimen_60)) / 3;//item寬高
int itemSpace=(int) getResources().getDimension(R.dimen.dimen_10);//item間隔
int marginTop = (getResources().getDimensionPixelSize(R.dimen.recycle_margin_top)
+ itemWidth * row
+itemSpace*(row-1)
+ getResources().getDimensionPixelSize(R.dimen.bottom_margin_top));
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) llBottom.getLayoutParams();
params.setMargins(0, marginTop, 0, 0);
llBottom.setLayoutParams(params);
}
3.scrollView包裹editText處理其滾動沖突
/**
* created by dalang at 2018/11/28
*/
@SuppressLint("AppCompatCustomView")
public class EditTextWithScrollView extends EditText {
//滑動距離的最大邊界
private int mOffsetHeight;
//是否到頂或者到底的標(biāo)志
private boolean mBottomFlag = false;
private boolean mCanVerticalScroll;
public EditTextWithScrollView(Context context) {
super(context);
init();
}
public EditTextWithScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public EditTextWithScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mCanVerticalScroll = canVerticalScroll();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
//如果是新的按下事件瀑粥,則對mBottomFlag重新初始化
mBottomFlag = false;
//如果已經(jīng)不要這次事件挣跋,則傳出取消的信號,這里的作用不大
if (mBottomFlag)
event.setAction(MotionEvent.ACTION_CANCEL);
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
if (mCanVerticalScroll) {
//如果是需要攔截狞换,則再攔截避咆,這個方法會在onScrollChanged方法之后再調(diào)用一次
if (!mBottomFlag)
getParent().requestDisallowInterceptTouchEvent(true);
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
return result;
}
@Override
protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
if (vert == mOffsetHeight || vert == 0) {
//這里觸發(fā)父布局或祖父布局的滑動事件
getParent().requestDisallowInterceptTouchEvent(false);
mBottomFlag = true;
}
}
/**
* EditText豎直方向是否可以滾動
*
* @return true:可以滾動 false:不可以滾動
*/
private boolean canVerticalScroll() {
//滾動的距離
int scrollY = getScrollY();
//控件內(nèi)容的總高度
int scrollRange = getLayout().getHeight();
//控件實際顯示的高度
int scrollExtent = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
//控件內(nèi)容總高度與實際顯示高度的差值
mOffsetHeight = scrollRange - scrollExtent+5;
if (mOffsetHeight == 0) {
return false;
}
return (scrollY > 0) || (scrollY < mOffsetHeight - 1);
}
}
4.底部文字點擊事件處理
相關(guān)代碼如下
在 OnRecyclerItemClickListener中添加方法
public abstract void onOtherClick(MotionEvent e);
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onItemClick(childViewHolder);
} else {
//判斷點擊的不是圖片 走這個方法
onOtherClick(e);
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onLongClick(childViewHolder);
}
}
}
//將textview固定高度好計算
<dimen name="bottom_textview_height">60dp</dimen>
<TextView
style="@style/demo6_text_style"
android:text="所在位置" />
//原有 textview style更改
<style name="demo6_text_style">
<item name="android:gravity">center</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">@dimen/bottom_textview_height</item>
<item name="android:textColor">@color/black33</item>
<item name="android:textSize">@dimen/dimen_18</item>
</style>
//初始化高度屬性
//textview 高度
bottomItemHeight = getResources().getDimensionPixelSize(R.dimen.bottom_textview_height);
//textview之間分割線 高度
lineSpace = (int) getResources().getDimension(R.dimen.dimen_1);
//左邊距
leftMargin = (int)getResources().getDimension(R.dimen.dimen_20);
//同步到控件 星星的寬度 圖片大小為30dp 左右各留10dp 方便用戶點擊
starWidth =(int) getResources().getDimension(R.dimen.dimen_50);
//
private void fixBottom() {
之前方法省略
//用于判斷 在每次fix底部布局高度后判斷 注意要減去頂部edittext的高度
judgeClickMargin = marginTop-getResources().getDimensionPixelSize(R.dimen.edittext_height);
}
//Activity中處理
@Override
public void onOtherClick(MotionEvent e) {
if (e.getY()>judgeClickMargin) {
int between=(int)e.getY()-judgeClickMargin;//判讀觸摸點與 bottom布局分界處的距離
int oneItem=(bottomItemHeight+lineSpace);//一個textview+一個分割線的高度
LogUtil.e(e.getY()+"---"+judgeClickMargin+"==="+oneItem);
if (between>0 && between<=oneItem) {
//點擊在第一個textview上 ---所在位置
ToastUtil.normal("所在位置");
} else if (between>oneItem && between<=2*oneItem) {
//點擊在第二個textview上 ---誰可以看
ToastUtil.normal("誰可以看");
} else if (between>2*oneItem && between<=3*oneItem) {
//點擊在第三個textview上 ---提醒誰看
ToastUtil.normal("提醒誰看");
} else if (between>3*oneItem && between<=4*oneItem && e.getX()>=leftMargin && e.getX()<=(starWidth+leftMargin)) {
//點擊星星 同步到空間
ToastUtil.normal("同步到空間");
}
}
}
其他文章鏈接地址:
(一)高斯模糊實現(xiàn)毛玻璃效果丶共享元素動畫 丶地址選擇器
(二)仿京東頂部伸縮漸變丶自定義viewpager指示器丶viewpager3D回廊丶recyclerview瀑布流
(三)RxJava2常用操作符merge、flatmap修噪、zip--結(jié)合MVP架構(gòu)講解
(四)仿支付寶首頁頂部伸縮滑動/中間層下拉刷新
(五)TabLayout+ViewPager懸浮吸頂及刷新數(shù)量動畫顯示
(六)仿QQ首頁drawer/側(cè)滑刪除/浮動imgaeView/角標(biāo)拖拽
將持續(xù)更新.. 不喜勿噴查库,僅個人分享,希望能幫助到你
源碼地址:Github傳送門