Android之自定義ListView(一)


PS:自定義View是Android中高手進階的路線.因此我也打算一步一步的學習.看了鴻洋和郭霖這兩位大牛的博客,決定一步一步的學習,循序漸進.

 

學習內容:

1.自定義View實現ListView的Item左右滑動顯示和隱藏彈窗的效果

 

  自定義View其實是在Android學習路上比較難掌握的一個重要點,但是也是高手的必經之路,自定義View分為很多種,我們可以直接繼承View,或者是繼承他的直接子類或間接子類.ViewGroup,ListView,LinearLayout,Button等等.繼承他們的間接子類還算是比較簡單的..因為View的子類或者是間接子類可以幫助我們做很多的事情.有很多的地方,我們可以不去實現,子類就幫我們做了(onMeasure,onLayout,onDraw)這些方法.直接繼承View,我們是必須要對這些方法進行重寫的,來實現我們自定義View.

  因此我這里就沒有去直接繼承View,而是選擇繼承了他的子類.由於ListView的使用還是相當的廣泛的,也是有些費勁的.因此決定從ListView開始..這里先把自定義的ListView代碼先粘出來.然后再一步一步的分析.

package com.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;
import com.example.administrator.myview.R;

/**
 * Created by totem on 2016/7/10.
 * @author 代碼丶如風
 */
public class MyListView extends ListView {


    private static final String TAG = "ListView";

    private LayoutInflater inflater;

    /**
     * 手指按下的x,y坐標,以及移動以后的x,y坐標
     */
    private int xDown;

    private int yDown;

    private int xMove;

    private int yMove;

    private boolean isRightSliding;
    private boolean isLeftSliding;

    //滑動的最小距離
    private int touchSlop;

    //PopWindow彈窗
    private PopupWindow popupWindow;
    private int popWindowWidth;
    private int popWindowHeight;

    private Button delButton;

    private int mCurrentViewPosition;
    private View mCurrentView;

    //回調接口
    private DeleteItemListener deleteItemListener;

    /**
     * 初始化操作
     */
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflater = LayoutInflater.from(context);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        View view = inflater.inflate(R.layout.delete_item, null);
        delButton = (Button) view.findViewById(R.id.id_item_btn);
        popupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        popupWindow.getContentView().measure(0, 0);
        popWindowWidth = popupWindow.getContentView().getMeasuredWidth();
        popWindowHeight = popupWindow.getContentView().getMeasuredHeight();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int action = event.getAction();
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                xDown = x;
                yDown = y;
                if (popupWindow.isShowing()) {
                    dismissPopWindow();
                }
                mCurrentViewPosition = pointToPosition(xDown, yDown);
                View view = getChildAt(mCurrentViewPosition - getFirstVisiblePosition());
                mCurrentView = view;
                break;
            case MotionEvent.ACTION_MOVE:
                xMove = x;
                yMove = y;
                int offsetX = xDown - xMove;
                int offsetY = yDown - yMove;
                if (xMove < xDown && Math.abs(offsetX) > touchSlop && Math.abs(offsetY) < touchSlop) {
                    isLeftSliding = true;
                }else if(xMove >xDown && Math.abs(offsetX) >touchSlop && Math.abs(offsetY) < touchSlop){
                    isRightSliding = true;
                }
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getAction();
        if (isLeftSliding) {
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    int location[] = new int[2];
                    mCurrentView.getLocationOnScreen(location);
                    popupWindow.setAnimationStyle(R.style.popwindow_delete_btn_anim_style); //設置彈窗的動畫效果
                    popupWindow.update();
                    popupWindow.showAtLocation(mCurrentView, Gravity.LEFT | Gravity.TOP, location[0] + mCurrentView.getWidth(), location[1] + mCurrentView.getHeight() / 2
                            - popWindowHeight / 2);
                    delButton.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (deleteItemListener != null) {
                                deleteItemListener.DeleteItem(mCurrentViewPosition);
                                popupWindow.dismiss();
                            }
                        }
                    });
                    break;
                case MotionEvent.ACTION_UP:
                    isLeftSliding = false;
                    break;
            }
            //防止與Item點擊事件沖突
            return true;
        }else if(isRightSliding){
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    if(popupWindow.isShowing()){
                        dismissPopWindow();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    isRightSliding = false;
                    break;
            }
            return true;
        }
        return super.onTouchEvent(event);
    }

    public void setDeleteItemListener(DeleteItemListener listener) {
        deleteItemListener = listener;
    }

    public interface DeleteItemListener {
        void DeleteItem(int position);
    }

    private void dismissPopWindow() {
        if (popupWindow != null && popupWindow.isShowing()) {
            popupWindow.dismiss();
        }
    }
}

  這里一部分是鴻洋大牛的.我又簡單的優化了點.他寫的只有向左滑動彈出,而沒有向右滑動隱藏.因此我這里就給加上了.主要還是理解其中的思想才是關鍵.畢竟自定義View的學習沒有什么相關的書籍,只能看這些牛人的博客了.看完也是受益匪淺的.廢話不多說,我們總體縷一下思路,如何去實現這個自定義View是關鍵.

 首先:

 我們要清楚,用戶的操作,怎樣才算是滑動效果,怎樣算是從左往右划,怎樣算從右往左划,這也是問題的第一個關鍵所在,那么我們知道用戶滑動的時候首先是需要點擊屏幕的,因此這里定義了xDown和yDown來記錄手指點下的坐標.那么滑動完以后,必然有一個結束滑動的坐標,xMove,yMove.相比到這里我們就明確怎樣是向左滑動和向右滑動了.

 xMove - xDown > 0 ? 向左滑動:向右滑動。。同時這里還要滿足xMove - xDown要大於最小的滑動距離,這里最小的滑動距離就是用touchslop來記錄的即:touchslop = ViewConfiguration.get(context).getScaledTouchSlop().這樣我們就先解決了第一個問題,如何判斷滑動.

 其次:

 滑動之后需要彈出一個窗口,那么這里就使用PopWindow來實現了.因此這里就創建了一個PopWindow.這個PopWindow可以顯示出滑動后的刪除按鈕,因此在new的時候,需要把相關的view附加上.

/**
     * <p>Create a new non focusable popup window which can display the
     * <tt>contentView</tt>. The dimension of the window must be passed to
     * this constructor.</p>
     *
     * <p>The popup does not provide any background. This should be handled
     * by the content view.</p>
     *
     * @param contentView the popup's content
     * @param width the popup's width
     * @param height the popup's height
     */
    public PopupWindow(View contentView, int width, int height) {
        this(contentView, width, height, false);
    }

  源碼是這樣寫的,創建一個無焦點的PopWindow用於顯示我們傳入的contentView.在這里這個PopWindow用於顯示刪除按鈕.但是我們需要明確一個地方.這個PopWindow是我們new出來的,並沒有在xml文件中進行書寫,我們在獲取它的寬高時,需要調用measure()方法,先對這個PopWindow進行測量,測量之后我們才能夠拿到相應的寬度和高度,因為這個PopWindow並沒有在我們的ListView中,也沒有在Item中,而是我們手動加上的,因此ListView在onMeasure()的時候是不會對這個PopWindow進行測量的.這個取決於View的加載機制,這里我先不進行多說,等到后期我會補上View的加載機制,如果讀者想現在弄明白怎么回事,推薦先去看看郭林大牛的博客,關於View的四篇文章,讀完那四篇文章,就能夠理解這塊到底是怎么回事了.反正在這里讀者只需要先記住就可以,不調用measure()方法是拿不到寬高的.讀者可以自己去試一下.好了,這樣我們就解決了第二個問題.

 最后:回調接口

 首先,我們在MyView中引入了一個按鈕,也就是PopWindow顯示的按鈕,但是這個按鈕需要做事情,它需要在被點擊的時候移除掉當前的Item,我們知道只有主線程才有權利對View進行操作,我們定義的這個View是沒有權利的,這樣就需要一個回調接口,在Button被點擊的時候出發回調事件,觸發的時候需要傳遞position參數,也就是當前Item的position.這個position傳入后,被稱為登記回調函數.觸發的同時登記回調函數告知主線程這個事件被觸發了,需要主函數進行處理,那么主線程在對Item進行操作,這樣就符合規則了,因此回調函數這個概念想必大家就清楚了.

 前一陣子在知乎上看到有解釋這個的概念,覺得說的非常的合理,在這里放上,這樣就更方便大家的理解了.

 回調函數是什么?

 鏈接:http://www.zhihu.com/question/19801131/answer/13005983  來源:知乎

 你到一個商店買東西,剛好你要的東西沒有貨,於是你在店員那里留下了你的電話,過了幾天店里有貨了,店員就打了你的電話,然后你接到電話后就到店里去取了貨。在這個例子里,你的電話號碼就叫回調函數,你把電話留給店員就叫登記回調函數,店里后來有貨了叫做觸發了回調關聯的事件,店員給你打電話叫做調用回調函數,你到店里去取貨叫做響應回調事件.

 然后事件分發機制了.dispatchTouchEvent和onTouchEvent,其實中間還有一個interpectTouchEvent,用於判斷是否需要對事件進行攔截,這個就不說了,對ACTION_DOWN,ACTION_UP,ACTION_DOWN事件進行處理.dispatchTouchEvent用於分發事件,只在這里進行了簡單的操作,對當前被點擊的Item進行記錄,以及對isRightSliding,isLeftSliding屬性進行賦值,分發之后就交給了onTouchEvent去處理.它來完成對ACTION的處理.這樣我們的總體思路就非常清晰了.

 首先:對滑動事件的判斷,其次:加入我們滑動后需要顯示的效果,接着添加相應的回調函數用來處理View的觸發事件,最后對事件分發處理,就解決了自定義View.最后附上MainActivity的源代碼.

package com.example.administrator.myview;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import com.view.MyListView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends Activity {

    private MyListView myListView;
    private ArrayAdapter<String> adapter;
    private List<String> mData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView(){
        myListView = (MyListView) findViewById(R.id.MyListView);
        mData = new ArrayList<>(Arrays.asList("1","2","3","4","5","6","7","8","9","10"));
        adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,mData);
        myListView.setAdapter(adapter);
        myListView.setDeleteItemListener(new MyListView.DeleteItemListener() {
            @Override
            public void DeleteItem(int position) {
                adapter.remove(adapter.getItem(position));
            }
        });
        myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //Item的點擊事件
            }
        });
    }
}

  我們看到MainActivity實現回調接口,然后對View進行處理,與我們所說一致.這樣就OK了,這里只簡單的寫了個Adapter,如果想寫更復雜的就自己去實現Adapter了.

 博客園的文件上傳限制在10M,沒有辦法只能分享個百度雲的鏈接了.

 源代碼:http://pan.baidu.com/s/1o8KVlXK

 

 

  
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM