先介紹一下本文出現的背景,項目中有這樣一個需求,拖動一個列表的某一項排序或者拖動到另外一個列表中。
其效果如下圖:
拖動排序在RecyclerView有現成的解決方案,配合ItemTouchHelper即可輕鬆實現。但是要將一個列表中的Item拖動到另一個列表中去,就不那麼容易了。
先來看看這種跨界面實現由那些難點,我們先分析,再一個一個的解決。
- 拖動視圖的保存,ItemTouchHelper的實現思路是獲取到長按的View後,對這個View做Translate位移,但是這樣做的侷限在於這個View就只能在它所屬的RecyclerView中移動,超出的部分就被隱藏了。
- 當拖動Item到邊緣後,兩個界面怎麼切換,是使用系統的ViewPager還是自定義ViewGroup。
- 在一個RecyclerView中拖動和跨多個拖動用到的策略是否一樣。
- 由於RecyclerView已經接收了觸摸事件,那麼怎麼獲取觸摸信息,比如判斷滑到邊緣了,以及Item是否長按。
現在我們來一個一個的解決上面的問題:
1. 對於第一個問題,解決方法是當判斷Item被長按後,將當前Item記錄爲一張圖片,然後設置給一個隱藏的ImageView,對這個ImageView做Translate平移。這裏需要額外費心的是這個ImageView與長按Item視圖的重合以及顯隱判斷。
2. 如果使用ViewPager,由於要跨越兩個page,那麼觸摸事件,保存的圖片等都要交給其父控制器,還不如自定義一個ViewGroup。
3. 由於系統提供了在一個RecyclerView中拖動的Helper類,我們無需去解決拖動的起始位置這些,那何樂不爲呢?所以,跨界面拖動用自己的實現。
4. 觸摸point信息的獲取:dispatchTouchEvent方法是最先到的,在我們自定義的ViewPager中重寫該方法。在adapter中對itemview添加手勢監聽,獲取到被長按的View。
下面來看代碼實現:
(1) 自定義的父視圖:
public class NewLinearLayout extends LinearLayout {
/*將觸摸點座標傳遞出去*/
private IGetX iGetX;
/*彈性滑動*/
private Scroller mScroller;
public NewLinearLayout(Context context) {
super(context);
init(context);
}
public NewLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NewLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/*initial*/
public void init(Context context){
mScroller = new Scroller(context);
}
@Override
public void computeScroll(){
super.computeScroll();
/*判斷是否完成滑動*/
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX() , 0);
/*循環調用該方法*/
postInvalidate();
}
}
/*開啓彈性滑動*/
public void beginScroll(int dx ){
mScroller.startScroll(mScroller.getCurrX() , 0 , dx , 0 , 1000);
/*調用computeScroll()方法*/
invalidate();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event){
float dx = event.getX();
float dy = event.getY();
if(event.getAction() == MotionEvent.ACTION_MOVE)
iGetX.point(dx , dy);
return super.dispatchTouchEvent(event);
}
public void set(IGetX iGetX){
this.iGetX = iGetX;
}
public interface IGetX{
void point(float dx , float dy);
}
}
(2) View轉bitmap
public Bitmap view2bitmap(View view) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
view.draw(new Canvas(bitmap));
return bitmap;
}
(3) Adapter的實現
private class Recycler1Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private ArrayList<String> mData;
public void setData(ArrayList<String> mData) {
this.mData = mData;
}
public void addData(ArrayList<String> mData) {
this.mData.addAll(mData);
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(SpanDragActivity.this).inflate(R.layout.item_launch_title, null);
final Recycler1ViewHolder viewHolder = new Recycler1ViewHolder(itemView);
viewHolder.itemView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mDetector.onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_DOWN){
bitmap = null;
TempItemView = viewHolder.itemView;
startX = TempItemView.getLeft();
startY = TempItemView.getTop();
}
/*恢復計點*/
if(event.getAction() == MotionEvent.ACTION_UP){
isFirstPoint = true;
}
return false;
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((Recycler1ViewHolder) holder).mTv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData.size();
}
/*create viewholder*/
class Recycler1ViewHolder extends RecyclerView.ViewHolder {
private TextView mTv;
public Recycler1ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_launch_title_tv_title);
}
}
}
(4) 滑動到邊緣後觸發的事件
/*設置接口獲取手指位置*/
activitySpanLl.set(new NewLinearLayout.IGetX() {
@Override
public void point(float dx , float dy) {
Log.v("out", "手指位置:" + dx);
if(isFirstPoint){
firstX = dx;
firstY = dy;
isFirstPoint = false;
}
if(bitmap != null) {
activitySpanImg.setTranslationX(dx-firstX+startX);
activitySpanImg.setTranslationY(dy-firstY+startY);
}
if(dx >=800){
}
if (isFirstScroll)
if (dx >= 1000) {
activitySpanLl.beginScroll(1080);
isFirstScroll = false;
/*向右邊滑動了,所以現在可以向左滑動了*/
isFirstLeftScroll = true;
int a = activitySpanLl.getScrollX();
float b = activitySpanLl.getX();
}
if(isFirstLeftScroll)
if(dx <= 80){
activitySpanLl.beginScroll(-1080);
isFirstLeftScroll = false;
/*向左邊滑動了,所以現在可以向右滑動了*/
isFirstScroll = true;
int a = activitySpanLl.getScrollX();
float b = activitySpanLl.getX();
}
}
});
基本思路和重要的代碼都在上面了,在實際項目中應用還需要對座標,滑動等作出優化。
有更多問題留言交流。
注意:
因爲電腦更換,本demo源碼已經找不到了,但是鑑於很多人需要,所以這周我重新寫一個demo,需要的先加這個QQ羣(之前由於一些原因博客停止更新,接下來會繼續更新,主要是音視頻相關的,也可以入羣交流)
703660703