1. 程式人生 > >ViewSwitcher的功能與用法

ViewSwitcher的功能與用法

ViewSwitcher代表了檢視切換元件,它本身繼承了FramLayout,因此可以將多個View層疊在一起,每次只顯示一個元件。當程式控制從一個View切換到另一個View時,ViewSwitcher支援指定動畫效果。

為了給ViewSwitcher新增多個元件,一般通過呼叫ViewSwitcher的setFactory(ViewSwitcher.ViewFactory)方法為之設定ViewFactory,並由該ViewFactory為之建立View即可。

例項:仿Android系統的Launcher介面

Android早期版本的Launcher介面是上下滾動的,新版Android的Launcher介面已經實現了分屏、左右滾動,本例就是通過ViewSwitcher來實現Android的分屏、左右滾動效果。

為了實現該效果,程式主介面考慮使用ViewSwitcher來組合多個GridView,每個GridView代表一個螢幕應用程式,GridView中每個單元格顯示一個應用程式的圖示和程式名。

佈局檔案如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <!--定義一個ViewSwitcher元件-->
    <ViewSwitcher
        android:id="@+id/viewSwitcher"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <!--定義滾動到上一屏的按鈕-->
    <Button
        android:id="@+id/button_prev"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginBottom="8dp"
        android:onClick="prev"
        android:text="&lt;"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>
    <!--定義滾動到下一屏的按鈕-->
    <Button
        android:id="@+id/button_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginBottom="8dp"
        android:onClick="next"
        android:text="&gt;"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
</android.support.constraint.ConstraintLayout>

上面的介面佈局檔案中定義了一個ViewSwitcher元件和兩個按鈕,這兩個按鈕分別用於控制該ViewSwitcher顯示上一屏和下一屏的程式列表。

該例項的重點在於為該ViewSwitcher設定ViewFactory物件,並且當用戶單擊“<”和“>”兩個按鈕時控制ViewSwitcher顯示上一屏和下一屏的應用程式。

該程式會考慮使用擴充套件BaseAdapter的方式為GridView提供Adapter,而本例項的關鍵就是根據使用者單擊的按鈕來動態計算該BaseAdapter應該顯示哪些程式列表。該程式的Activity程式碼如下。

public class MainActivity extends AppCompatActivity {

    //定義一個常量,用於顯示沒屏顯示的應用程式數
    public static final int NUMBER_PER_SCREEN = 12;
    //儲存系統所有應用程式的List集合
    private List<DataItem> items = new ArrayList<>();
    //記錄當前正在顯示第幾屏的程式
    private int screenNo = -1;
    //儲存程式所佔的總屏數
    private int screenCount = 0;
    private ViewSwitcher switcher;
    //建立LayoutInflater物件
    private LayoutInflater inflater;
    //該BaseAdapter負責為每屏顯示的GridView提供列表項

    private BaseAdapter adapter = new BaseAdapter() {
        @Override
        public int getCount() {
            //如果已經到了最後一屏,且因應用程式的數量不能整除NUMBER_PER_SCREEN
            if (screenNo == screenCount - 1 && items.size() % NUMBER_PER_SCREEN != 0) {
                //最後一屏顯示的程式數為應用程式的數量對NUMBER_PER_SCREEN求餘
                return items.size() % NUMBER_PER_SCREEN;
                //否則每屏顯示的程式數為NUMBER_PER_SCREEN
            } else {
                return NUMBER_PER_SCREEN;
            }
        }

        @Override
        public DataItem getItem(int position) {
            //根據screenNo計算第position個列表項的資料
            return items.get(screenNo * NUMBER_PER_SCREEN + position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;
            ViewHolder viewHolder;
            if (convertView == null) {
                //載入R.layout.labelicon佈局元件
                view = inflater.inflate(R.layout.labelicon, null);
                ImageView imageView = view.findViewById(R.id.imageview);
                TextView textView = view.findViewById(R.id.textview);
                viewHolder = new ViewHolder(imageView, textView);
                view.setTag(viewHolder);
            } else {
                view = convertView;
                viewHolder = (ViewHolder)view.getTag();
            }
            //獲取R.layout.labelicon佈局檔案中的ImageView元件,併為之設定圖示
            viewHolder.imageView.setImageDrawable(getItem(position).drawable);
            //獲取R.layout.labelicon佈局檔案中的TextView元件,併為之設定文字
            viewHolder.textView.setText(getItem(position).dataName);
            return view;
        }
    };

    //代表應用程式的內部類
    public static class DataItem {
        //應用程式名稱
        String dataName;
        //應用程式圖示
        Drawable drawable;
        DataItem(String dataName, Drawable drawable) {
            this.dataName = dataName;
            this.drawable = drawable;
        }
    }

    //代表應用程式的內部類
    public static class ViewHolder {
        ImageView imageView;
        TextView textView;
        ViewHolder(ImageView imageView, TextView textView) {
            this.imageView = imageView;
            this.textView = textView;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        inflater = LayoutInflater.from(this);
        //建立一個包含40個元素的List集合,用於模擬包含40個應用程式
        for (int i = 0; i < 40; i++) {
            String label = "" + i;
            Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher, null);
            DataItem item = new DataItem(label, drawable);
            items.add(item);
        }

        //計算應用程式所佔的總屏數
        //如果應用程式的數量能整除NUMBER_PER_SCREEN,除法的結果就是總屏數
        //如果不能整除,總屏數應該是除法的結果再加1
        screenCount = items.size() % NUMBER_PER_SCREEN == 0 ? items.size() / NUMBER_PER_SCREEN :
                items.size() / NUMBER_PER_SCREEN + 1;
        switcher = findViewById(R.id.viewSwitcher);
        switcher.setFactory(() -> {
            //載入R.layout.slidelistview元件,實際上就是一個GridView元件
            return inflater.inflate(R.layout.slidelistview, null);
        });
        //頁面載入時先顯示第一屏
        next(null);
    }
    public void next (View v) {
        if (screenNo < screenCount - 1) {
            screenNo++;
            //為ViewSwitcher的元件顯示過程設定動畫
            switcher.setInAnimation(this, R.anim.slide_in_right);
            //為ViewSwitcher的元件隱藏過程設定動畫
            switcher.setOutAnimation(this, R.anim.slide_out_left);
            //控制下一屏將要顯示的GridView對應的Adapter
            ((GridView) switcher.getNextView()).setAdapter(adapter);
            //點選右邊按鈕,顯示下一屏
            //學習手勢檢測後,也可通過手勢檢查實現顯示下一屏
            switcher.showNext();
        }
    }

    public void prev(View v) {
        if (screenNo > 0) {
            screenNo--;
            //為ViewSwitcher的元件顯示過程設定動畫
            switcher.setInAnimation(this, R.anim.slide_in_right);
            //為ViewSwitcher的元件隱藏過程設定動畫
            switcher.setOutAnimation(this, R.anim.slide_out_left);
            //控制下一屏將要顯示的GridView對應的Adapter
            ((GridView) switcher.getNextView()).setAdapter(adapter);
            //點選右邊按鈕,顯示下一屏
            //學習手勢檢測後,也可通過手勢檢查實現顯示下一屏
            switcher.showPrevious();
        }
    }
}

上面的程式使用screenNo儲存當前正在顯示第幾屏的程式列表。該程式的關鍵在於粗體字程式碼部分,該粗體字程式碼建立了一個BaseAdpater物件,這個BaseAdpater會根據screenNo動態計算該Adapter總共包含多少個列表項(如getCount()方法所示),會根據screenNo計算每個列表項的資料(如getItem(int position)方法所示)。

BaseAdpater的getView()只是簡單載入了R.layout.labelicon佈局檔案,並使用當前列表項的圖片資料填充R.layout.labelico佈局檔案中的ImageView,使用當前列表項的文字資料填充R.layout.labelicon佈局檔案中的TextView。下面是R.layout.labelicon的程式碼。

<?xml version="1.0" encoding="utf-8"?>
<!-- 定義一個垂直的LinearLayout,該容器中放置一個ImageView和一個TextView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center" />
</LinearLayout>

當用戶點選“>”按鈕時,程式的事件處理方法將會控制viewSwitcher呼叫showNext()方法顯示下一屏的程式列表,而且此時screenNo被加1,因而Adapter將會動態計算下一屏的程式列表,再將該Adapter傳給ViewSwitcher接下來要顯示的GridView。

為了實現ViewSwitcher切換View時的動畫效果,程式的事件處理方法中呼叫了ViewSwitcher的setInAnimation()、setOutAnimation()方法來設定動畫效果。本程式不僅利用了Android系統提供的兩個動畫資源,還自行提供了動畫資源。

R.anim.slide_in_right動畫資原始碼如下

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 設定從右邊拖進來的動畫
    android:duration指定動畫持續時間  -->
    <translate
        android:fromXDelta="100%p"
        android:toXDelta="0"
        android:duration="@android:integer/config_mediumAnimTime" />
</set>

R.anim.slide_in_left動畫資原始碼如下

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 設定從左邊拖出去的動畫 
    android:duration指定動畫持續時間 -->
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%p"
        android:duration="@android:integer/config_mediumAnimTime" />
</set>