Android中的自定義控制項(二)

来源:http://www.cnblogs.com/huangjie123/archive/2016/12/01/6123575.html
-Advertisement-
Play Games

創建一個自定義的開關,可以自行決定開關的背景。當滑動開關時,開關的滑塊可跟隨手指移動。當手指鬆開後,滑塊根據開關的狀態,滑到最右邊或者滑到最左邊,同時保存開關的狀態,將開關的狀態回調給調用者。 ...


案例四: 自定義開關
       功能介紹:本案例實現的功能是創建一個自定義的開關,可以自行決定開關的背景。當滑動開關時,開關的滑塊可跟隨手指移動。當手指鬆開後,滑塊根據開關的狀態,滑到最右邊或者滑到最左邊,同時保存開關的狀態,將開關的狀態回調給調用者。當然,上述功能系統給定的switch控制項也可以實現。
      實現步驟:
        1. 寫一個類繼承view,重寫兩個參數的構造方法。在構造方法中指定工作空間,通過attrs.getAttributeResourceValue方法將java代碼中的屬性值和xml中的屬性值聯繫起來。這樣可以在xml文件中指定相關的屬性值。重寫onmeasure和ondraw方法,繪製圖片。這裡測量圖片大小直接用setMeasuredDimension方法,獲取圖片本身的大小。
        2. 設置介面回調。對於圖片來說,我們希望能夠在調用者獲取開關的狀態,因此需要設置一個介面回調,用於監控開關的狀態,當開關的狀態發生變化時間調用。介面回調的優勢在於調用者並不知道何時調用,所以在另一個文件中設置一個介面,在該文件觸發事件。由於重寫了介面的方法,因此,執行重寫後的方法。這樣就可以實現數據的回調。自定義控制項中介面回調的應用較為廣泛,幾乎所有的控制項都需要設置監聽,且寫法較為固定。
        3. 重寫ontouchevent()方法。分析得知,開關由兩部分組成,一部分是底座兒,一部分是劃片而。當手指滑動時,劃片兒應該跟隨手指移動。當劃片的左邊小於底座左邊坐標時,讓劃片左邊的坐標和底座對齊,當劃片的右邊大於底座右邊坐標時,讓劃片右邊的坐標和底座對齊,這樣保證劃片不越界。當手指鬆開後,判斷劃片的中線坐標是在底座兒中線坐標的左邊還是右邊,以此來決定劃片最終是停在左邊還是右邊。同時改變開關的狀態,將開關的狀態回調給控制項的調用中,讀取開關的狀態。

       代碼實現。 主程式(調用者)中的代碼:

package com.example.aswitch;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private MyToggleButton toggleButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toggleButton = (MyToggleButton) findViewById(R.id.toggle_button);
        toggleButton.setOnStateChangedListener(new MyToggleButton.OnStateChangedListener() {
            @Override
            public void onStateChanged(boolean state) {
                Toast.makeText(MainActivity.this, state ? "開" : "關", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

 

        自定義開關的具體實現;

package com.example.aswitch;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by huang on 2016/12/1.
 */
public class MyToggleButton extends View {
    private Bitmap background;
    private Bitmap slideIcon;
    private boolean state;
    private OnStateChangedListener mOnStateChangedListener;
    private int backgroundWidth;
    private int backgroundHeight;
    private int slideIconWidth;
    private int slideIconHeight;
    private int slideIconLeft;
    private int maxSlideIconLeft;

    public MyToggleButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        String namespace = "http://schemas.android.com/apk/res-auto";
        int slideBackgroundResId = attrs.getAttributeResourceValue(namespace, "slideBackground", -1);
        int slideIconResId = attrs.getAttributeResourceValue(namespace, "slideIcon", -1);
        if (slideBackgroundResId != -1 && slideIconResId != -1) {
            setSwitchImage(slideBackgroundResId, slideIconResId);
        }

        boolean state = attrs.getAttributeBooleanValue(namespace, "state", false);
        setState(state);
    }

    /**
     * 設置開關的圖片
     * @param slideBackgroundResId 開關的背景圖片資源id
     * @param slideIconResId 開關上面的滑塊icon
     */
    public void setSwitchImage(int slideBackgroundResId, int slideIconResId) {
        background = BitmapFactory.decodeResource(getResources(), slideBackgroundResId);
        slideIcon = BitmapFactory.decodeResource(getResources(), slideIconResId);

        backgroundWidth = background.getWidth();
        backgroundHeight = background.getHeight();
        slideIconWidth = slideIcon.getWidth();
        slideIconHeight = slideIcon.getHeight();

        maxSlideIconLeft = backgroundWidth - slideIconWidth;
    }

    /** 設置開關按鈕的狀態 */
    public void setState(boolean state) {
        checkState(state);
        if (state) {
            slideIconLeft = maxSlideIconLeft;
        } else {
            slideIconLeft = 0;
        }
    }

    public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {
        this.mOnStateChangedListener = mOnStateChangedListener;
    }

    /** 開關按鈕狀態改變的監聽器 */
    public interface OnStateChangedListener {
        void onStateChanged(boolean state);
    }

    /**
     * 對View進行測量的方法
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(backgroundWidth, backgroundHeight);
    }

    /** 把View畫出來的方法
     * @param canvas 畫布
     * */
    @Override
    protected void onDraw(Canvas canvas) {
        int left = 0;
        int top = 0;
        canvas.drawBitmap(background, left, top, null);

        canvas.drawBitmap(slideIcon, slideIconLeft, 0, null);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                slideIconLeft = (int) (event.getX() - slideIconWidth / 2);

                if (slideIconLeft < 0) {
                    slideIconLeft = 0;
                } else if (slideIconLeft > maxSlideIconLeft) {
                    slideIconLeft = maxSlideIconLeft;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (event.getX() < backgroundWidth / 2) {
                    slideIconLeft = 0;
                    checkState(false);
                } else {
                    slideIconLeft = maxSlideIconLeft;
                    checkState(true);
                }
                break;
        }
        invalidate();
        return true;
    }

    private void checkState(boolean state) {
        if (this.state != state) {
            this.state = state;

            if (mOnStateChangedListener != null) {
                mOnStateChangedListener.onStateChanged(state);
            }
        }
    }
}

 

        佈局文件的編寫:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:huang="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.aswitch.MyToggleButton
        android:id="@+id/toggle_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        huang:slideBackground="@mipmap/slide_background2"
        huang:slideIcon="@mipmap/slide_icon2"
        huang:state="false" />

</RelativeLayout>

       為了使佈局文件中的屬性有作用,還要單獨在values文件夾中寫一個attrs.xml文件,聲明相關的屬性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyToggleButton">
        <attr name="slideBackground" format="reference" />
        <attr name="slideIcon" format="reference" />
        <attr name="state" format="boolean" />
    </declare-styleable>
</resources>

 

 

 

    案例五:下拉刷新的Listview
       功能介紹: 系統本身的Listview預設情況下是沒有下拉刷新和上拉載入更多的功能。但是Listview有addHeadview和addFootView方法,因此可以為listview加頭佈局或者腳佈局。對於頭佈局來說,有三個狀態,下拉刷新、鬆開刷新、正在刷新,同時伴有相應的動畫予以提示。刷新成功後,為listview添加一套信息。同樣的腳佈局載入成功後,在listview的最後一條數據後面,在載入一條信息。
       實現步驟:
         1. 寫佈局文件。這裡除了在主界面中需要一個Listview外,還需要一個頭部的佈局文件和一個尾部的佈局文件。初始狀態時,頭佈局和腳佈局均是隱藏的。當滑動事件出發的時候,調整頭佈局和腳佈局的位置。這裡。頭佈局和腳佈局的根表簽最好是使用Linearlayout進行包裹,否則在滑動顯示是容易出現問題。
         2. 寫一個類繼承listview。初始化界面佈局,通過View.measure(0, 0)方法主動觸發測量,mesure內部會調用onMeasure,從而獲取到頭佈局的高度。如步驟一所說,開始狀態時,頭佈局是隱藏的,所以為頭佈局設置panding值,來使得頭佈局隱藏,只需要把setpadding中的第二個參數設置為負的頭文件的高。同樣的,顯示頭佈局只需要把setpadding中的第二個參數設置為0。對於根佈局的顯示和隱藏同樣採用上述的方法。當然也可以改變setpadding的第四個參數來控制顯示和隱藏。
         3. 重寫OntouchEvent()方法。手指按下時,記錄下初始位置。手指滑動時在記錄一個y坐標。通過兩次坐標的差值,判斷手指滑動的方向。在可見的第一個條目的position為0的時候,如果手指向下滑動,有兩種狀態,一個是下拉刷新,指的是頭佈局從不可見到剛好全部可見,一個是鬆開刷新,頭佈局全部可見後繼續向下拖動,就會進入鬆開刷新狀態。另外還有一種狀態就是正在刷新,這種狀態下,頭佈局剛好停留在頂部,維持一段時間。實際開發中,正在刷新是向伺服器請求網路。這裡添加的是假數據。不同的狀態之間的轉換還伴隨有動畫效果,因此,這裡還寫有補間動畫來實現向上和向下的效果。
         4. 寫一個滑動監聽事件,在監聽事件中進行腳佈局邏輯的編寫。當滿足以下三種情況的時候則顯示腳佈局,一個ListView處於空閑狀態,另一個是界面上可見的最後一條item是ListView中最後的一條item,還有一個是如果當前沒有去做正在載入更多的事情。顯示腳佈局的時候,不僅要設置padding值,同時選中listview最後一個條目,這樣就可以在界面上完整的顯示。
         5. 最後要註意的是,無論是同佈局,還是腳佈局,都要對狀態進行修改。所以,可以增加一個回調監聽。表明數據刷新或者載入完成,可以隱藏頭佈局和腳佈局了,同時,頭佈局的狀態恢復為下拉刷新,腳佈局不是正在載入。同時,停掉相關的動畫,修改顯示的狀態。

         主程式(調用者)的編寫。一般刷新是請求資料庫數據,這裡直接給出模擬數據演示效果。

package com.example.pulltofreshlistview;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;

import com.example.pulltofreshlistview.view.PullToRefreshListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    private PullToRefreshListView listView;
    private ArrayList<String> datas;
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (PullToRefreshListView) findViewById(R.id.list_view);

        datas = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            datas.add("我又撿到錢了,好開心啊^_^ \t" + i);
        }

        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datas);
        listView.setAdapter(adapter);

        listView.setOnRefreshingListener(new PullToRefreshListView.OnRefreshingListener() {
            @Override
            public void onRefreshing() {
                reloadData();
            }

            @Override
            public void onLoadMore() {
                loadMore();
            }
        });
    }

    /**
     * 重新聯網獲取數據
     */
    protected void reloadData() {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                datas.add(0, "我是刷新出來的數據");
                adapter.notifyDataSetChanged();
                listView.onRefreshComplete();
            }
        }, 3000);
    }

    /**
     * 聯網載入更多數據
     */
    protected void loadMore() {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                datas.add("我是載入更多出來的數據");
                adapter.notifyDataSetChanged();
                listView.onLoadmoreComplete();
            }
        }, 3000);
    }
}

            

        下拉刷新listview的邏輯部分

package com.example.pulltofreshlistview.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.pulltofreshlistview.R;

/**
 * Created by huang on 2016/12/1.
 */


public class PullToRefreshListView extends ListView {

    private View headerView;
    private float downY;
    private int headerViewHeight;
    private static final int STATE_PULL_TO_REFRESH = 0;
    private static final int STATE_RELEASE_REFRESH = 1;
    private static final int STATE_REFRESHING = 2;
    private int currentState = STATE_PULL_TO_REFRESH;    // 預設是下拉刷新狀態
    private ImageView iv_arrow;
    private ProgressBar progress_bar;
    private TextView tv_state;
    private RotateAnimation upAnim;
    private RotateAnimation downAnim;
    private OnRefreshingListener mOnRefreshingListener;
    private View footerView;
    private int footerViewHeight;
    private boolean loadingMore;

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initHeaderView();
        initFooterView();
    }

    private void initHeaderView() {
        headerView = View.inflate(getContext(), R.layout.header_view, null);
        iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
        progress_bar = (ProgressBar) headerView.findViewById(R.id.progress_bar);
        showRefreshingProgressBar(false);
        tv_state = (TextView) headerView.findViewById(R.id.tv_state);
        headerView.measure(0, 0);
        headerViewHeight = headerView.getMeasuredHeight();
        hideHeaderView();
        super.addHeaderView(headerView);
        upAnim = createRotateAnim(0f, -180f);
        downAnim = createRotateAnim(-180f, -360f);
    }

    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.footer_view, null);
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        hideFooterView();
        super.addFooterView(footerView);

        super.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
                        && getLastVisiblePosition() == getCount() - 1
                        && loadingMore == false
                        ) {
                    loadingMore = true;
                    showFooterView();
                    setSelection(getCount() - 1);

                    if (mOnRefreshingListener != null) {
                        mOnRefreshingListener.onLoadMore();
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    private void hideFooterView() {
        int paddingTop = -footerViewHeight;
        setFooterViewPaddingTop(paddingTop);
    }

    private void showFooterView() {
        int paddingTop = 0;
        setFooterViewPaddingTop(paddingTop);
    }

    private void setFooterViewPaddingTop(int paddingTop) {
        footerView.setPadding(0, paddingTop, 0, 0);
    }

    /**
     * 設置顯示進度的圈圈
     *
     * @param showProgressBar 如果是true,則顯示ProgressBar,否則的話顯示箭頭
     */
    private void showRefreshingProgressBar(boolean showProgressBar) {
        progress_bar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
        iv_arrow.setVisibility(!showProgressBar ? View.VISIBLE : View.GONE);

        if (showProgressBar) {
            iv_arrow.clearAnimation();    // 有動畫的View要清除動畫才能真正的隱藏
        }
    }

    /**
     * 創建旋轉動畫
     *
     * @param fromDegrees 從哪個角度開始轉
     * @param toDegrees   轉到哪個角度
     * @return
     */
    private RotateAnimation createRotateAnim(float fromDegrees, float toDegrees) {
        int pivotXType = RotateAnimation.RELATIVE_TO_SELF;
        int pivotYType = RotateAnimation.RELATIVE_TO_SELF;
        float pivotXValue = 0.5f;
        float pivotYValue = 0.5f;
        RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
        ra.setDuration(300);
        ra.setFillAfter(true);
        return ra;
    }

    /**
     * 隱藏HeaderView
     */
    private void hideHeaderView() {
        int paddingTop = -headerViewHeight;
        setHeaderViewPaddingTop(paddingTop);
    }

    /**
     * 顯示HeaderView
     */
    private void showHeaderView() {
        int paddingTop = 0;
        setHeaderViewPaddingTop(paddingTop);
    }

    /**
     * 設置HeaderView的paddingTop
     *
     * @param paddingTop
     */
    private void setHeaderViewPaddingTop(int paddingTop) {
        headerView.setPadding(0, paddingTop, 0, 0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (currentState == STATE_REFRESHING) {
                    return super.onTouchEvent(ev);
                }

                int fingerMoveDistanceY = (int) (ev.getY() - downY);        // 手指移動的距離
                if (fingerMoveDistanceY > 0 && getFirstVisiblePosition() == 0) {
                    int paddingTop = -headerViewHeight + fingerMoveDistanceY;
                    setHeaderViewPaddingTop(paddingTop);

                    if (paddingTop < 0 && currentState != STATE_PULL_TO_REFRESH) {
                        currentState = STATE_PULL_TO_REFRESH;
                        tv_state.setText("下拉刷新");
                        iv_arrow.startAnimation(downAnim);
                        showRefreshingProgressBar(false);
                    } else if (paddingTop >= 0 && currentState != STATE_RELEASE_REFRESH) {
                        currentState = STATE_RELEASE_REFRESH;
                        tv_state.setText("鬆開刷新");
                        iv_arrow.startAnimation(upAnim);
                        showRefreshingProgressBar(false);

                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (currentState == STATE_RELEASE_REFRESH) {
                    currentState = STATE_REFRESHING;
                    tv_state.setText("正在刷新");
                    showRefreshingProgressBar(true);
                    showHeaderView();

                    if (mOnRefreshingListener != null) {
                        mOnRefreshingListener.onRefreshing();
                    }
                } else if (currentState == STATE_PULL_TO_REFRESH) {
                    hideHeaderView();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnRefreshingListener(OnRefreshingListener mOnRefreshingListener) {
        this.mOnRefreshingListener = mOnRefreshingListener;
    }

    /**
     * ListView刷新的監聽器
     */
    public interface OnRefreshingListener {
        void onRefreshing();

        void onLoadMore();
    }

    /**
     * 聯網刷新數據的操作已經完成了
     */
    public void onRefreshComplete() {
        hideHeaderView();
        currentState = STATE_PULL_TO_REFRESH;
        showRefreshingProgressBar(false);
    }

    /**
     * 載入更多新數據的操作已經完成了
     */
    public void onLoadmoreComplete() {
        hideFooterView();
        loadingMore = false;
    }
}

 

          佈局文件包含三個部分,listview主體部分:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.pulltofreshlistview.view.PullToRefreshListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

         頭佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center_vertical" >

    <RelativeLayout
        android:layout_width="50dp"
        android:layout_height="50dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/arrow"
            android:layout_centerInParent="true"/>

        <ProgressBar
            android:id="@+id/progress_bar"
            style="@android:style/Widget.ProgressBar"
            android:indeterminateDrawable="@drawable/progress_medium_red"
            android:layout_width="28dp"
            android:layout_height="28dp"
            android:layout_centerInParent="true"
            android:visibility="gone"/>

    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FF0000"
            android:text="下拉刷新"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#666666"
            android:textSize="12sp"
            android:text="最後刷新時間:2015-07-25 19:59:39"
            android:layout_marginTop="4dp"/>

    </LinearLayout>

</LinearLayout>

 

        腳佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <ProgressBar
        style="@android:style/Widget.ProgressBar"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:indeterminateDrawable="@drawable/progress_medium_red" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:text="載入更多..."
        android:textColor="#FF0000" />

</LinearLayout>

 

 


案例六:   側滑菜單
       功能分析: 之前的案例大部分都只有一個控制項,因此是通過繼承view實現。側滑菜單顯然需要兩個控制項才能實現,一個用於載入主界面,一個用於載入側滑界面。所以這裡需要繼承viewgroup。通過在主界面滑動來控制界面的顯示和隱藏。
       實現步驟:
          1. 佈局文件的書寫。佈局文件包含兩個部分,主界面和側滑界面。主界面中有一個標題欄,標題欄有圖片按鈕,文字組成。圖片按牛同樣可以控制側邊欄的顯示和隱藏。側邊欄在ScrollView中添加一個Linearlayout,在linearlayout中縱向排列textview顯示文本。當然,側滑菜單同樣可以用listview顯示,這裡為簡單起見就不採用listview顯示。此外,TextView要想響應點擊事件,需要設置clickable為true。
          2. 創建一個類繼承於ViewGroup。重寫onmeasure()方法,測量測量控制項大小,包括兩個子控制項。這裡,側滑菜單的寬在佈局文件中寫好了,主界面的寬適配手機界面。所以分別調用menu.measure(menuWidth, heightMeasureSpec)和main.measure(widthMeasureSpec, heightMeasureSpec)方法即可獲取。排版容器中的子View。由於該自定義的控制項包含不止一個子view,所以重寫onlayout()方法是必不可少的。對子View進行排版,子View的0,0坐標是SlidingMenu的左上角。對側滑菜單進行排版,菜單的left坐標在負的菜單寬的位置,菜單的top坐標在0的位置,菜單的right坐標在0的位置,菜單的bottom坐標在容器的最底邊。對主界面進行排版,主界面的left坐標在0的位置,主界面的top坐標在0的位置,主界面的right坐標在容器的最右邊,主界面的bottom坐標在容器的最底邊。
          3. 重寫onInterceptTouchEvent。本案例中只關心水平划動,所以如果水平移動距離比垂直移動距離大,則認為是水平移動,把事件攔截,不讓ScrollView使用,此事件交由控制項本身處理。這裡有必要介紹一下事件分發。視圖集合對於事件的分發,自上而下處理。ViewGroup擁有下麵3個Touch相關方法,dispatchTouchEvent(MotionEvent ev)用於Touch事件的頒發,onInterceptTouchEvent(MotionEvent ev) 用於攔截Touch事件,onTouchEvent(MotionEvent event) 用於處理Touch事件。這裡重寫了onInterceptTouchEvent(),相應的還要重寫ontouchevent()方法。
          4. 在ontouchevent()方法中實現滑動跟隨的邏輯。滑動事件中,只關心橫向滑動。按下時,記錄下當前的x坐標。滑動時,再次記錄當前x坐標,兩者相減得到移動的距離。通過scrollTo()方法滑到相應的位置。系統給定的scrollTo()方法預設向右為負值,所以可重寫scrollTo()方法保證移動為正值。代碼優化。其實上述過程可以實現側滑菜單的功能。當滑動過程略顯生硬。這時就可以用到Scroller,這個類專門用於模擬滾動的數值。同時要配合computeScroll()方法使用。
          5. 還有一個菜單的開關按鈕,要麼開,要麼關。開的時候完全顯示側滑菜單。關的時候,隱藏側滑菜單。這點邏輯和ontouchevent()中手指抬起的邏輯一樣。 

          主程式(調用者)的編寫:

package com.example.slidingmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private SlidingMenu sliding_menu;
    private LinearLayout ll_menu;
    private TextView tv_news;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        sliding_menu = (SlidingMenu) findViewById(R.id.sliding_menu);
        ll_menu = (LinearLayout) findViewById(R.id.ll_menu);
        tv_news = (TextView) findViewById(R.id.tv_news);
        setCurrentSelectedMenuItem(tv_news);
    }

    /** 設置當前選擇的菜單Item */
    private void setCurrentSelectedMenuItem(View menuItem) {
        for (int i = 0; i < ll_menu.getChildCount(); i++) {
            View child = ll_menu.getChildAt(i);
            child.setSelected(child == menuItem);
        }
    }

    /** 菜單列表中的某個菜單項被單擊了 */
    public void onMenuItemClick(View v) {
        TextView textView = (TextView) v;
        Toast.makeText(this, textView.getText(), Toast.LENGTH_SHORT).show();
        setCurrentSelectedMenuItem(v);
    }

    /** 主界面上的菜單按鈕被單擊了 */
    public void onMenuToggleClick(View v) {
        sliding_menu.toggle();
    }

}

         側滑菜單的主體邏輯。

package com.example.slidingmenu;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by huang on 2016/12/1.
 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 開篇語 作為一個前端的菜鳥,總是在面試的時候被問及一些優化的問題,說實話,回答的時候總是心虛,因為確實沒有真真正正做過優化對比,也不知道從何說起,今天在網上收集一些例子加以總結,日後若是開發用到,也能一一對照,把自己開發的網站做到最優化! 網站優化 一個網站的優化,前端只是很小的一部分。大到系統架構 ...
  • // <![CDATA[ function init() { var $$ = go.GraphObject.make; myDiagram = $$(go.Diagram, "myDiagramDiv", { grid: $$(go.Panel, "Grid", $$(go.Shape, "Lin ...
  • <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> #box { border: 2px solid deeppink; width: 1000px; margin: ...
  • /*socket.io是一個基於 node.js 的項目,其主要作用是將WebScoket 協議應用到所有的瀏覽器。該模塊主要應用於實時的長連接 多請求的項目中,例如線上聯網游戲中、實時聊天、實施股票查看、二維碼掃描登錄等。 安裝配置和 npm 一樣: npm intall scoket.io 安裝 ...
  • 正常情況下直接寫這段代碼不寫!important,如果項目不能生效,需要加!important。 ...
  • 這是一段用jquery實現全選的代碼,主要思路如下: 1.所有的覆選框都有單擊事件,所有效果都是在單擊事件下實現的 2.全選覆選框所實現的功能與其他覆選選項實現的功能不同,所有在單擊事件內做一個判斷,是否是全選覆選框的單擊事件 3.如果是,則執行判斷全選覆選框是否選中,如果當前狀態為選中,那麼點擊後 ...
  • 之前關於如何實現屏幕頁面切換,寫過一篇博文《Android中使用ViewFlipper實現屏幕切換》,相比ViewFlipper,ViewPager更適用複雜的視圖切換,而且Viewpager有自己的adapter,這也讓其適應複雜對象,實現數據的動態載入。 ViewPager是谷歌官方給我們提供的 ...
  • 一、切UIView的某個角為圓角 如果需要將UIView的4個角全部都為圓角,做法相當簡單,只需設置其Layer的cornerRadius屬性即可(項目需要使用QuartzCore框架)。而若要指定某幾個角(小於4)為圓角而別的不變時,怎麼做呢? 其實很簡單,使用UIBezierPath,設置CAS ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...