[Android] 仿网易新闻客户端分类排序

效果图

先来看看网易新闻客户端以及自己实现的效果图,效果当然还是网易的好


wangyixinwen.gif
gridviewsort.gif

如何实现拖拽一个Item

WindowManager添加一个ImageView,并且将这个ImageView的显示图片设置成被拖拽item的截图,截图可以通过ViewgetDrawingCache获得。拖拽的时候,隐藏原始的item。处理触摸事件的ActionMove,调整ImageView的位置,跟随手指移动。在ActionUp的时候removeView

GridView

 @Override
    public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l)
    {
        // 至少有两个item的时候,才有排序
        if (getChildCount() >= 2)
        {
            mView = view;
            // 在调用getDrawingCache必须先调用
            view.setDrawingCacheEnabled(true);
            // 获取截图并设置
            Bitmap bitmap = view.getDrawingCache();
            mDragItemView.setImageBitmap(bitmap);
            // 设置拖拽的imageview的params
            mDragItemLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
            mDragItemLayoutParams.width = bitmap.getWidth();
            mDragItemLayoutParams.height = bitmap.getHeight();
            mDragItemLayoutParams.x = (mDownX - mDragItemLayoutParams.width / 2);
            mDragItemLayoutParams.y = (mDownY - mDragItemLayoutParams.height / 2);
            // 设置拖拽imageview的中心位于长按点击点
            mDragItemLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  //不接受按键事件
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE  // 不接收触摸事件
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON   // 保持常亮
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; // place the window within the entire screen, ignoring decorations around the border (such as the status bar)
            mDragItemLayoutParams.format = PixelFormat.TRANSLUCENT;
            mDragItemLayoutParams.windowAnimations = 0;
            // 往WindowManager中添加拖拽的View
            mWindowManager.addView(mDragItemView, mDragItemLayoutParams);

            ((GridViewSortAdapter) getAdapter()).init();

            ((GridViewSortAdapter) getAdapter()).hideView(i);

            Log.d(TAG, "long click = " + i);

            mDragStarted = true;
        }
        return true;
    }
@Override
public boolean onTouchEvent(MotionEvent ev)
{
   switch (ev.getAction() & ev.getActionMasked())
   {
       case MotionEvent.ACTION_DOWN:
           mDownX = (int) ev.getRawX();
           mDownY = (int) ev.getRawY();
           break;

       case MotionEvent.ACTION_MOVE:
           if (mDragStarted)
           {
                // 保持中心
               mDragItemLayoutParams.x = (int) (ev.getRawX() - mDragItemView.getWidth() / 2);
               mDragItemLayoutParams.y = (int) (ev.getRawY() - mDragItemView.getHeight() / 2);
               // 更新params
               mWindowManager.updateViewLayout(mDragItemView, mDragItemLayoutParams);
               // ......
           }
           break;

       case MotionEvent.ACTION_UP:
           // ......
           break;
   }
   return super.onTouchEvent(ev);
}

如何实现隐藏拖拽的Item

在开始拖拽的时候,把隐藏的item的position告诉Adapter,调用AdapternotifyDataSetChanged刷新数据,在getView方法中判断当前的构建的item的position是不是需要隐藏的position是的话就设置viewinVisible

GridView

@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l)
{
    // ......
    ((GridViewSortAdapter) getAdapter()).hideView(i);
    // ......
}

GridViewSortAdapter

public void hideView(int item)
{
    // ......
    mStartHideItemPosition = item;
    notifyDataSetChanged();
}
private int mStartHideItemPosition = AdapterView.INVALID_POSITION;

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    ViewHolder holder = null;
    if (convertView == null)
    {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.view_item_grid_view_sort, null);
        holder = new ViewHolder();
        holder.title = (TextView) convertView.findViewById(R.id.view_item_grid_view_sort_title);
        convertView.setTag(holder);
    }
    else
    {
        holder = (ViewHolder) convertView.getTag();
    }
    holder.title.setText(mTypeTitle.get(position));
    if (mStartHideItemPosition == position)
    {
        convertView.setVisibility(View.INVISIBLE);
    }
    else
    {
        convertView.setVisibility(View.VISIBLE);
    }
    return convertView;
}

如何知道当前拖拽到哪一个item之上

要想在拖拽到其他item上面时互换位置,那必须得知道当前拖拽到了哪一个item之上。GrideView提供了一个方法叫pointToPosition,可以在处理触摸事件的ACTION_MOVE时,获取手指触摸的x,y来得到当前拖拽到item之上的position。这里需要注意的一点是,在拖拽的过程,同一个item的position是不会变的,除非调用了AdapternotifyDataSetChangedposition才会重新计算。比如position为2的item,在拖拽的过程无论怎么动画移动位置,他的position都是2,知道一次拖拽结束,ActionUp的时候,会调用notifyDataSetChanged

GridView

@Override
 public boolean onTouchEvent(MotionEvent ev)
 {
    case MotionEvent.ACTION_MOVE:
    if (mDragStarted)
    {
        // ......
        int position = pointToPosition((int) ev.getX(), (int) ev.getY());
        // ......
    }
    break;
}

如何实现动画

一个item需要水平以及垂直需要移动的距离可以事先先计算出来,其实水平距离不管怎么样一定会是GridView一个单元格的宽度加上水平间距,垂直距离无论如何都是一个单元格的高度加上垂直距离,宽度非常好取,高度的话,这里默认item 的高度和单元格的高度相同。

GridViewSortAdapter

View view = mGridView.getChildAt(0);
mTranslateX = view.getWidth() + mHorizontalSpace;
mTranslateY = view.getHeight() + mVerticalSpace;

当拖拽到其他item之上时,开始动画

SortGridView

if (position != AdapterView.INVALID_POSITION && !((GridViewSortAdapter) getAdapter()).isInAnimation())
{    
     Log.d(TAG, "position = " + position);    
     ((GridViewSortAdapter) getAdapter()).swap(position);
}

GridSortAdapter

public void swap(int position)
{
    mAnimatorSetList.clear();

    int r_p = mPositionList.indexOf(position);

    Log.d(TAG, "r_p = " + r_p);

    if (mCurrentHideItemPosition < r_p)
    {
        for (int i = mCurrentHideItemPosition + 1; i <= r_p; i++)
        {
            View v = mGridView.getChildAt(mPositionList.get(i));
            if (i % mColsNum == 0 && i > 0)
            {
                startMoveAnimation(v, v.getTranslationX() + mTranslateX * (mColsNum - 1), v.getTranslationY() -
                        mTranslateY);
            }
            else
            {
                startMoveAnimation(v, v.getTranslationX() - mTranslateX, 0);
            }
        }
    }
    else if (mCurrentHideItemPosition > r_p)
    {
        for (int i = r_p; i < mCurrentHideItemPosition; i++)
        {
            View v = mGridView.getChildAt(mPositionList.get(i));
            if ((i + 1) % mColsNum == 0)
            {
                startMoveAnimation(v, v.getTranslationX() - mTranslateX * (mColsNum - 1), v.getTranslationY() + mTranslateY);
            }
            else
            {
                startMoveAnimation(v, v.getTranslationX() + mTranslateX, 0);
            }
        }
    }

    resetPositionList();

    int value = mPositionList.get(mStartHideItemPosition);
    if (mStartHideItemPosition < r_p)
    {
        mPositionList.add(r_p + 1, value);
        mPositionList.remove(mStartHideItemPosition);
    }
    else if (mStartHideItemPosition > r_p)
    {
        mPositionList.add(r_p, value);
        mPositionList.remove(mStartHideItemPosition + 1);
    }

    mCurrentHideItemPosition = r_p;
}

public boolean isInAnimation()
{
    return mInAnimation;
}

private void resetPositionList()
{
    mPositionList.clear();
    for (int i = 0; i < mGridView.getChildCount(); i++)
    {
        mPositionList.add(i);
    }
}


private void startMoveAnimation(View myView, float x, float y)
{
    AnimatorSet set = new AnimatorSet();
    set.playTogether(
            ObjectAnimator.ofFloat(myView, "translationX", myView.getTranslationX(), x),
            ObjectAnimator.ofFloat(myView, "translationY", myView.getTranslationY(), y)
    );
    set.addListener(new Animator.AnimatorListener()
    {
        @Override
        public void onAnimationStart(Animator animator)
        {
            mInAnimation = true;
        }

        @Override
        public void onAnimationEnd(Animator animator)
        {
            mInAnimation = false;
        }

        @Override
        public void onAnimationCancel(Animator animator)
        {

        }

        @Override
        public void onAnimationRepeat(Animator animator)
        {

        }
    });
    mAnimatorSetList.add(set);
    set.setDuration(150).start();
}

这里我主要解释一下代码中 mPositionList这个列表的作用,之前说过一次拖拽的时候,item的position是不会变化的。

假设有一组数据

a b c d
e f g h
i j k l

此时mPositionList的内容就是 0 1 2 3 4 5 6 7 8 9 10 11 12
现在将c拖拽到g上,拖拽完成之后的数据应该是,未释放手指

a b d e
f g c h
i j k l

此时mPositionList的内容就是 0 1 2 4 5 6 7 3 8 9 10 11 12
紧接着,继续拖拽ce上,你会发现调用pointToPosition方法得到的position是5,但是e现在的索引是4
因此你只需要调用

mPositionList.indexOf(pointToPosition(x,y))

就能得到真实的item的position

其他

如果把GridView的列数变成1那么似曾相识啊


gridviewex.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容