新聞頻道管理的炫酷實現

自定義GridLayout控件,可以在新聞咨詢類APP中的管理頁面使用到,也可以應用在類別管理中,總之,可以幫助我們設計更加規范和炫酷的手機頁面。

新聞類app是最常見的應用之一,而頻道管理又是其必不可少的功能,該自定義控件不僅可以帶我們實現炫酷的頻道管理功能,還可以讓我們學習如何使用Android拖拽框架實現我們想要的多種功能,以及讓我們對自定義控件會有更多的理解。

知識點

  1. GridLayout的使用

    • 從Google官方文檔學習GridLayout的功能以及用法
    • 使用GridLayout實現子控件排列顯示
  2. View的拖拽功能實現

    • 通過查看Google文檔,學會調用view的拖拽方法
    • 拖拽事件的處理
    • 使用View的拖拽框架實現實現頻道切換位置效果
  3. 自定義GridLayout控件

    自定義GridLayout控件,實現拖拽功能,繼而實現頻道管理操作

  4. Rect類的使用

    使用Rect類確定被觸摸到的子控件

新聞頻道管理的多種實現

實現類似于網易新聞頻道管理有幾種方式

實現方案1:使用兩個GridView實現,這是比較早的一種解決方案,比較復雜,具體可以參考 Android 高仿 頻道管理----網易、今日頭條、騰訊視頻

實現方案2:使用兩個RecyclerView或ListView實現,這種實現方式好于于方案1

實現方案3:使用一個RecyclerView實現,這是我目前見過的比較好的方式,貌似比網易新聞等客戶端的要流暢,具體可以參考:高仿網易新聞欄目動畫效果使用ItemTouchHelper高效地實現 今日頭條 、網易新聞 的頻道排序、移動

實現方案4:使用兩個GridLayout實現,也就是本文要介紹的方式

拖拽

長按item開始拖拽

private View.OnLongClickListener longClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            //長按時,開始拖拽操作,顯示出陰影
            //被拖拽的視圖其實就是v參數
            dragedView = v;
            v.startDrag(null, new View.DragShadowBuilder(v), null, 0);
            v.setEnabled(false);
            // v.startDragAndDrop(null, new View.DragShadowBuilder(v), null, 0);
            return false;
        }
    };

拖拽方法參數說明

  • startDragAndDrop() api24
  • startDrag(ClipData data, DragShadowBuilder shadowBuilder, Object myLocalState, int flag)

參數1:ClipData data 拖拽過程中可以transferred的數據,可以為空

參數2:DragShadowBuilder shadowBuilder,拖拽陰影效果創建者

參數3:Object myLocalState,拖拽狀態

參數4:int flag,可以控制拖拽操作的flag,未定義,傳0即可

拖拽監聽

private View.OnDragListener dragListener =  new View.OnDragListener() {
        /**
         * ACTION_DRAG_STARTED:當拖拽操作執行時,就會執行一次
         * DragEvent.ACTION_DRAG_ENDED:當拖拽事件結束,手指抬起時,就是執行一次
         * DragEvent.ACTION_DRAG_ENTERED:當手指進入設置了拖拽監聽的控件范圍內的瞬間執行一次
         * DragEvent.ACTION_DRAG_EXITED:當手指離開設置了拖拽監聽的控件范圍內的瞬間執行一次
         * DragEvent.ACTION_DRAG_LOCATION:當手指在設置了拖拽監聽的控件范圍內,移動時,實時會執行,執行N次
         * DragEvent.ACTION_DROP:當手指在設置了拖拽監聽的控件范圍內松開時,執行一次
         *
         *
         * @param v 當前監聽拖拽事件的view(其實就是mGridLayout)
         * @param event 拖拽事件
         * @return
         */
        @Override
        public boolean onDrag(View v, DragEvent event) {
            String dragEventAction = getDragEventAction(event);
            System.out.println(dragEventAction);
            //            Rect rect = new Rect();
            //            rect.contains()
            switch (event.getAction()) {
                //當拖拽事件開始時,創建出與子控件對應的矩形數組
                case DragEvent.ACTION_DRAG_STARTED:
                    initRects();
                    break;
                case DragEvent.ACTION_DRAG_LOCATION:
                    //手指移動時,實時判斷觸摸是否進入了某一個子控件
                    int touchIndex = getTouchIndex(event);
                    //說明觸摸點進入了某一個子控件,判斷被拖拽的視圖與進入的子控件對象不是同一個的時候才進行刪除添加操作

                    if (touchIndex > -1&&dragedView != null&&dragedView != mGridLayout.getChildAt(touchIndex)) {
                        mGridLayout.removeView(dragedView);
                        mGridLayout.addView(dragedView,touchIndex);
                    }
                    break;
                case DragEvent.ACTION_DRAG_ENDED:
                    //拖拽事件結束后,讓被拖拽的view設置為可用,否則背景變紅,并且長按事件會失效
                    if (dragedView != null) {
                        dragedView.setEnabled(true);
                    }
                    break;
            }

            return true;
        }
    };

DragEvent

拖拽事件 說明
ACTION_DRAG_STARTED 當拖拽操作執行時,就會執行一次
DragEvent.ACTION_DRAG_ENDED 當拖拽事件結束,手指抬起時,就是執行一次
DragEvent.ACTION_DRAG_ENTERED 當手指進入設置了拖拽監聽的控件范圍內的瞬間執行一次
DragEvent.ACTION_DRAG_EXITED 當手指離開設置了拖拽監聽的控件范圍內的瞬間執行一次
DragEvent.ACTION_DRAG_LOCATION 當手指在設置了拖拽監聽的控件范圍內,移動時,實時會執行,執行N次
DragEvent.ACTION_DROP 當手指在設置了拖拽監聽的控件范圍內松開時,執行一次

當拖拽事件開始時,創建出與子控件對應的矩形數組

private Rect[] mRects;

    private void initRects() {
        mRects = new Rect[mGridLayout.getChildCount()];
        for (int i = 0; i < mGridLayout.getChildCount(); i++) {
            View childView = mGridLayout.getChildAt(i);
            //創建與每個子控件對應矩形對象
            Rect rect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
            mRects[i] = rect;
        }
    }

手指移動時,實時判斷觸摸是否進入了某一個子控件

private int getTouchIndex(DragEvent event) {
        //遍歷所有的數組,如果包含了當前的觸摸點返回索引即可
        for (int i = 0; i < mRects.length; i++) {
            Rect rect = mRects[i];
            if (rect.contains((int)event.getX(), (int)event.getY())) {
                return i;
            }
        }
        return -1;
    }

是否允許拖拽

public void setAllowDrag(boolean allowDrag) {
        this.allowdrag = allowDrag;
        if (this.allowdrag) {
            this.setOnDragListener(odl);
        } else {
            this.setOnDragListener(null);
        }

    }

設置列數和動畫

//初始化方法
private void init() {
    // android:columnCount="4"
    // android:animateLayoutChanges="true"
    this.setColumnCount(columnCount);
    this.setLayoutTransition(new LayoutTransition());
}

DragGridlayout

public class DragGridlayout extends GridLayout{

    private  static final int columnCount = 4;//列數
    private boolean isAllowDrag;//記錄當前控件是否可以進行拖拽操作

    public DragGridlayout(Context context) {
        this(context,null);
    }

    public DragGridlayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public DragGridlayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

/*  static SparseArray<String> dragEventType = new SparseArray<>();
    static{
        dragEventType.put(DragEvent.ACTION_DRAG_STARTED, "STARTED");
        dragEventType.put(DragEvent.ACTION_DRAG_ENDED, "ENDED");
        dragEventType.put(DragEvent.ACTION_DRAG_ENTERED, "ENTERED");
        dragEventType.put(DragEvent.ACTION_DRAG_EXITED, "EXITED");
        dragEventType.put(DragEvent.ACTION_DRAG_LOCATION, "LOCATION");
        dragEventType.put(DragEvent.ACTION_DROP, "DROP");
    }

    public static String getDragEventAction(DragEvent de){
        return dragEventType.get(de.getAction());
    }*/

    //初始化方法
    private void init() {
        //  android:columnCount="4"
        //  android:animateLayoutChanges="true"
        this.setColumnCount(columnCount);
        this.setLayoutTransition(new LayoutTransition());
    }

    public void setItems(List<String> items) {
        for (String item : items) {
            addItem(item);
        }
    }

    public void addItem(String content, int index) {
        TextView tv = newItemView();
        tv.setText(content);
        addView(tv,index);
    }

    public void addItem(String content) {
        TextView tv = newItemView();
        tv.setText(content);
        addView(tv);
    }

    private TextView newItemView() {
        TextView tv = new TextView(getContext());
        int margin = dip2px(5);
        tv.setBackgroundResource(R.drawable.selector_tv_bg);
        GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
        layoutParams.width = getResources().getDisplayMetrics().widthPixels/4 - 2*margin;//寬為屏幕寬的4分之一
        layoutParams.height = dip2px(25);
        layoutParams.setMargins(margin,margin,margin,margin);
        tv.setGravity(Gravity.CENTER);
        tv.setLayoutParams(layoutParams);

        if (isAllowDrag) {
            //給條目設置長按點擊事件
            tv.setOnLongClickListener(mLongClickListener);
        } else {
            tv.setOnLongClickListener(null);
        }

        //設置條目的點擊事件
        tv.setOnClickListener(onClickListener);
        return tv;
    }

    /** dip轉換px */
    public int dip2px(int dip) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dip * scale + 0.5f);
    }

    private View dragedView;//被拖拽的視圖

    private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            //長按時,開始拖拽操作,顯示出陰影
            //被拖拽的視圖其實就是v參數
            dragedView = v;
            v.startDrag(null, new View.DragShadowBuilder(v), null, 0);
            v.setEnabled(false);
            //v.startDragAndDrop(null, new View.DragShadowBuilder(v), null, 0); // api24
            return true;
        }
    };

    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(onDragItemClickListener != null){
                onDragItemClickListener.onDragItemClick((TextView) v);
            }
        }
    };


    public void setAllowDrag(boolean isAllowDrag) {
        this.isAllowDrag = isAllowDrag;
        if (this.isAllowDrag) {
            this.setOnDragListener(mDragListener);
        } else {
            this.setOnDragListener(null);
        }
    }

    private View.OnDragListener mDragListener =  new View.OnDragListener() {
        /**
         * ACTION_DRAG_STARTED:當拖拽操作執行時,就會執行一次
         * DragEvent.ACTION_DRAG_ENDED:當拖拽事件結束,手指抬起時,就是執行一次
         * DragEvent.ACTION_DRAG_ENTERED:當手指進入設置了拖拽監聽的控件范圍內的瞬間執行一次
         * DragEvent.ACTION_DRAG_EXITED:當手指離開設置了拖拽監聽的控件范圍內的瞬間執行一次
         * DragEvent.ACTION_DRAG_LOCATION:當手指在設置了拖拽監聽的控件范圍內,移動時,實時會執行,執行N次
         * DragEvent.ACTION_DROP:當手指在設置了拖拽監聽的控件范圍內松開時,執行一次
         *
         * @param v 當前監聽拖拽事件的view(其實就是mGridLayout)
         * @param event 拖拽事件
         * @return
         */
        @Override
        public boolean onDrag(View v, DragEvent event) {
            switch (event.getAction()) {
                //當拖拽事件開始時,創建出與子控件對應的矩形數組
                case DragEvent.ACTION_DRAG_STARTED:
                    initRects();
                    break;
                case DragEvent.ACTION_DRAG_LOCATION:
                    //手指移動時,實時判斷觸摸是否進入了某一個子控件
                    int touchIndex = getTouchIndex(event);
                    //說明觸摸點進入了某一個子控件,判斷被拖拽的視圖與進入的子控件對象不是同一個的時候才進行刪除添加操作

                    if (touchIndex > -1 && dragedView != null && dragedView != DragGridlayout.this.getChildAt(touchIndex)) {
                        DragGridlayout.this.removeView(dragedView);
                        DragGridlayout.this.addView(dragedView,touchIndex);
                    }
                    break;
                case DragEvent.ACTION_DRAG_ENDED:
                    //拖拽事件結束后,讓被拖拽的view設置為可用,否則背景變紅,并且長按事件會失效
                    if (dragedView != null) {
                        dragedView.setEnabled(true);
                    }
                    break;
            }

            return true;
        }
    };

    //手指移動時,實時判斷觸摸是否進入了某一個子控件
    private int getTouchIndex(DragEvent event) {
        //遍歷所有的數組,如果包含了當前的觸摸點返回索引即可
        for (int i = 0; i < mRects.length; i++) {
            Rect rect = mRects[i];
            if (rect.contains((int)event.getX(), (int)event.getY())) {
                return i;
            }
        }
        return -1;
    }


    //當拖拽事件開始時,創建出與子控件對應的矩形數組
    private Rect[] mRects;

    private void initRects() {
        mRects = new Rect[this.getChildCount()];
        for (int i = 0; i < this.getChildCount(); i++) {
            View childView = this.getChildAt(i);
            //創建與每個子控件對應矩形對象
            Rect rect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
            mRects[i] = rect;
        }
    }

    private OnDragItemClickListener onDragItemClickListener;

    public interface OnDragItemClickListener{
        public void onDragItemClick(TextView tv);
    }

    public void setOnDragItemClickListener(OnDragItemClickListener onDragItemClickListener) {
        this.onDragItemClickListener = onDragItemClickListener;
    }
}

MainActivity

布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:id="@+id/activity_main"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:gravity="center_vertical"
            android:onClick="addItem"
            android:text="添加條目"
            android:textColor="?android:attr/textColorPrimary"/>

        <com.github.draggridlayout.DragGridlayout
            android:id="@+id/selectedChannel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="16dp"/>


        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:background="?attr/colorButtonNormal"
            android:gravity="center_vertical"
            android:padding="5dp"
            android:text="點擊選擇頻道"
            android:textColor="?android:attr/textColorPrimary"/>

        <com.github.draggridlayout.DragGridlayout
            android:id="@+id/unSelectedChannel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"/>

    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

代碼實現

public class MainActivity extends AppCompatActivity {

    private DragGridlayout mSelectedChannel;
    private DragGridlayout mUnSelectedChannel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViews();
        initData();
        initEvent();
    }

    private void initViews() {
        setContentView(R.layout.activity_main);
        mSelectedChannel = (DragGridlayout) findViewById(R.id.selectedChannel);
        mUnSelectedChannel = (DragGridlayout) findViewById(R.id.unSelectedChannel);
        mSelectedChannel.setAllowDrag(true);
        mUnSelectedChannel.setAllowDrag(true);
    }

    private void initData() {
        List<String> selectedChannel = new ArrayList<>();
        selectedChannel.add("頭條");
        ...
        mSelectedChannel.setItems(selectedChannel);

        List<String> unSelectedChannel = new ArrayList<>();
        unSelectedChannel.add("NBA");
        ...
        mUnSelectedChannel.setItems(unSelectedChannel);
    }

    public void initEvent(){
        //設置條目點擊監聽
        mSelectedChannel.setOnDragItemClickListener(new DragGridlayout.OnDragItemClickListener() {
            @Override
            public void onDragItemClick(TextView tv) {
                //移除點擊的條目,把條目添加到下面的Gridlayout
                mSelectedChannel.removeView(tv);//移除是需要時間,不能直接添加
                mUnSelectedChannel.addItem(tv.getText().toString(),0);
            }
        });

        mUnSelectedChannel.setOnDragItemClickListener(new DragGridlayout.OnDragItemClickListener() {
            @Override
            public void onDragItemClick(TextView tv) {
                //移除點擊的條目,把條目添加到上面的Gridlayout
                mUnSelectedChannel.removeView(tv);//移除是需要時間,不能直接添加
                mSelectedChannel.addItem(tv.getText().toString());
            }
        });
    }

    private int index = 0;

    public void addItem(View view) {
        mSelectedChannel.addItem("頻道" + index++,0);
    }

}

源代碼

https://github.com/JackChan1999/DragGridLayout

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容