Android 精通自定義視圖(2)

本項目Demo: https://github.com/liaozhoubei/CustomViewDemo

輪播圖的實現(xiàn)

現(xiàn)在世面上的app非常流行的一個功能,輪播圖效果如下:

myviewpager.gif

輪播圖實現(xiàn)的原理是借由ViewPager這個V4包中的類實現(xiàn)的,要在layout布局中使用它,需要寫出它的絕對路徑,layout代碼如下:

<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" >

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="160dp" >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        android:background="#66000000"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="5dp" >

        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:text="天王蓋地虎, 天王蓋地虎, 天王蓋地虎, "
            android:textColor="@android:color/white" />

        <LinearLayout
            android:id="@+id/ll_point_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:orientation="horizontal" >
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

</RelativeLayout>

我們的輪播圖由三部分組成,第一部分是底層的輪換圖片,第二部分是需要輪換的文字標題,最后就是輪換文字下面的小白點。

輪播圖的具體實現(xiàn)代碼:

public class MyViewpager extends Activity {
private int[] imageResIds;
private String[] contentDescs;
private ViewPager mViewPager;
private TextView mTv_desc;
private LinearLayout mLl_point_container;
private ArrayList<ImageView> mImageViewList;
private int previousSelectedPosition = 0;
private Timer timer;
private MyPagerListener myPagerListener;
private boolean isRunning = true; // 判斷Activity是否仍在運行,以便在Activity銷毀時結束自動循環(huán)
private long delay = 3 * 1000; // 設置輪播圖延時時間

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_myviewpager);
    initView();
    initData();
    initAdapter();

    // 實現(xiàn)viewpager自動循環(huán)的方法
    timer = new Timer();
    timer.schedule(new TimerTask() {

        @Override
        public void run() {
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
                }
            });

        }
    }, 2000, delay); // 第一次仔細在兩秒之后,然后每個3秒執(zhí)行一次
}

private void initView() {
    // 初始化視圖
    mViewPager = (ViewPager) findViewById(R.id.viewpager);
    mTv_desc = (TextView) findViewById(R.id.tv_desc);
    mLl_point_container = (LinearLayout) findViewById(R.id.ll_point_container);
    myPagerListener = new MyPagerListener();
    mViewPager.addOnPageChangeListener(myPagerListener);
}

private void initData() {
    // 初始化要顯示的數(shù)據(jù)

    // 圖片資源id數(shù)組
    imageResIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e };

    // 文本描述
    contentDescs = new String[] { "鞏俐不低俗,我就不能低俗", "撲樹又回來啦!再唱經典老歌引萬人大合唱", "揭秘北京電影如何升級", "樂視網TV版大派送", "熱血屌絲的反殺" };

    mImageViewList = new ArrayList<ImageView>();
    ImageView imageView;
    View pointView;
    for(int i = 0; i < imageResIds.length; i++) {
        imageView = new ImageView(getApplicationContext());
        // 設置viewpager的圖像
        imageView.setBackgroundResource(imageResIds[i]);
        mImageViewList.add(imageView);
        // 創(chuàng)建小圓點,當可用的時候為白色,不可用的時候為灰色
        pointView = new View(getApplicationContext());
        // 設置小圓點的圖像
        pointView.setBackgroundResource(R.drawable.selector_bg_point);
        // 設置小圓點的大小
        LayoutParams params = new LayoutParams(5, 5);
        if (i != 0) {
            // 當小圓點不是處于第一位的時候每個相隔10像素
            params.leftMargin = 10;
        }
        // 設置小圓點為不可以,當前狀態(tài)為灰色
        pointView.setEnabled(false);
        mLl_point_container.addView(pointView, params);

    }

}

private void initAdapter() {
    // 設置小圓點小白點第一個位置的為白色
    mLl_point_container.getChildAt(0).setEnabled(true);
    // 設置開始時的文字標題
    mTv_desc.setText(contentDescs[0]);
    // 初始化上一次記錄位置為0
    previousSelectedPosition = 0;

    mViewPager.setAdapter(new MyPagerAdapter());
    // 使用Integer.MAX_VALUE可能會產生BUG,因此可以直接使用500000
    int position = Integer.MAX_VALUE / 2 - (Integer.MAX_VALUE / 2 % mImageViewList.size());
    // 設置viewpager當前的條目位置,設置大數(shù)值充當無限循環(huán)的角色
    mViewPager.setCurrentItem(5000000);
}

private class MyPagerAdapter extends PagerAdapter{

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        // 指定復用的判斷邏輯, 固定寫法
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
            //返回要顯示的條目內容, 創(chuàng)建條目

        // 設置但position大于mImageViewList.size()的時候重新從0開始,避免數(shù)組越界
        int newPosition = position % mImageViewList.size();
        // a. 把View對象添加到container中
        ImageView imageView = mImageViewList.get(newPosition);
        // b. 把View對象返回給框架, 適配器
        container.addView(imageView);
        return imageView; // 必須重寫, 否則報異常
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // object 要銷毀的對象
        container.removeView((View) object);
    }

}

private class MyPagerListener implements OnPageChangeListener{

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // 滑動的時候調用
    }

    @Override
    public void onPageSelected(int position) {
        // 新的條目被選中時調用

        // 設置但position大于mImageViewList.size()的時候重新從0開始
        int newPosition = position % mImageViewList.size();
        // 設置標題和白色小圓點的位置
        mTv_desc.setText(contentDescs[newPosition]);            
        mLl_point_container.getChildAt(previousSelectedPosition).setEnabled(false);
        mLl_point_container.getChildAt(newPosition).setEnabled(true);
        previousSelectedPosition = newPosition;            
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        // 滑動狀態(tài)發(fā)生改變的時候調用
    }

}

@Override
protected void onDestroy() {
    super.onDestroy();
    //界面銷毀時結束定時任務
    timer.cancel();
    // 結束監(jiān)聽頁面滑動
    mViewPager.removeOnPageChangeListener(myPagerListener);

}

}

代碼解析

輪播圖實現(xiàn)的代碼雖然看上去很多,實質上就只有這幾個要點:

  • 使用ViewPager,并且給ViewPager設置PagerAdapter,實現(xiàn)輪播圖的效果,代碼如下:

      mViewPager.setAdapter(new MyPagerAdapter());
    
  • 監(jiān)聽ViewPager的滑動事件MyPagerListener,設置ViewPager滑動時的變化,代碼如下:

      mViewPager.addOnPageChangeListener(myPagerListener);
    

當ViewPager的圖片滑動的時候,同步更新頁面中的標題和白色小圓點的舉例

  • 設置ViewPager圖片自動輪換,在這里我們使用Timer定時任務來執(zhí)行,代碼如下:

              timer = new Timer();
      timer.schedule(new TimerTask() {
    
          @Override
          public void run() {
              runOnUiThread(new Runnable() {
    
                  @Override
                  public void run() {
                      mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
                  }
              });
    
          }
      }, 2000, delay);
    

好了,介紹完大概的方法后,我們來詳細的解析一下代碼吧。

與listView的使用方法差不多,制作輪播圖一樣要設置一個Adapter,但是ViewPager要使用的卻是PagerAdapter,它必須重寫PagerAdapter的四個方法:

    getCount()
    isViewFromObject()
    instantiateItem()
    destroyItem()

getCount()方法是為了獲取所傳入內容的多少,isViewFromObject()則是指定復用的判斷邏輯, 一般是固定寫return view == object就可以了。instantiateItem()則是初始化要顯示的內容,destroyItem()則是銷毀移出視線的內容。

在instantiateItem()我們沒有處理過多的內容,僅僅是獲得了通過輪播圖的位置來獲得輪播圖,而這個輪播圖的位置position則是通過前面mViewPager.setCurrentItem(5000000)這行代碼獲得的。
為什么要將ViewPager的條目設置成5000000,因為我們要使viewpager滑動的時候給用戶形成一種這是無限循環(huán)的錯覺,當我們 設置了一個足夠大的數(shù)字時,用戶滑動很長的時間都不會滑倒Item的盡頭,更不會造成數(shù)組越界。
同時通過以下代碼:

    int newPosition = position % mImageViewList.size();
    ImageView imageView = mImageViewList.get(newPosition);

來獲得之前設置在List集合之中的數(shù)據(jù),并且newPosition的大小適中在0 - mImageViewList.size()直接,從而使得list數(shù)組不會有數(shù)組越界的問題。

事實上,當我們設置完PagerAdapter之后,我們就已經完成了輪播圖,實現(xiàn)了輪播圖的效果了。剩下的只是輪播圖的一些掃尾工作,也就是添加每個輪播圖的標題,以及展示輪播圖當前位置標識的小白點。

輪播圖的標題以及其底下幾個小白點實質上與Adapter是分隔開的,它所形成的效果是通過viewpager設置的OnPageChangeListener改變的。OnPageChangeListener中的onPageSelected方法,當圖片頁面被選擇的時候就會調用,每次調用的時候我們就獲得了當前視圖的條目position,然后通過它來確定當前的文字和白點所顯示的位置。

這里順帶說一下小白點是如何出現(xiàn)的并且更換顏色的。

        // 創(chuàng)建小圓點,當可用的時候為白色,不可用的時候為灰色
        pointView = new View(getApplicationContext());
        // 設置小圓點的圖像
        pointView.setBackgroundResource(R.drawable.selector_bg_point);
        // 設置小圓點的大小
        LayoutParams params = new LayoutParams(5, 5);
        if (i != 0) {
            // 當小圓點不是處于第一位的時候每個相隔10像素
            params.leftMargin = 10;
        }
        // 設置小圓點為不可以,當前狀態(tài)為灰色
        pointView.setEnabled(false);
        mLl_point_container.addView(pointView, params);

在之前的layout布局中我們設置了一個空的LinearLayout并且設置id為ll_point_container,這個就是包含小白點的線性布局,然后我們通過用代碼的方式創(chuàng)建了白色原點,那么我們怎么控制它是白色還是灰色呢?我們是直接通過一個狀態(tài)選擇來設置的,當設置setEnabled(true)的時候是白色,為false時就是灰色,狀態(tài)選擇器代碼如下:

    <selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@drawable/shape_bg_point_disenable" android:state_enabled="false"></item>
<item android:drawable="@drawable/shape_bg_point_enable" android:state_enabled="true"></item>

    </selector>

至此,輪播圖的實現(xiàn)便到此為止了,話說回來,輪播圖的實現(xiàn)也沒有我們想象中的那么難嘛~~~

下拉選擇框的實現(xiàn)

對于下拉選擇框,其實android也有一個spinner控件來實現(xiàn),但是這個控件所能實現(xiàn)的功能并不多,所以才需要自定義一個下拉選擇框,效果如下圖:

MyPopupwindow.gif

要實現(xiàn)下拉選擇框,其核心使用PopupWindow和ListView這兩個類。下拉選擇空中有一系列的條目,其形式都是一樣的,這種狀況之下,自然是使用ListView了,但是我們想要的是展示選擇框,那么就不可能將Listview直接放在一個布局中。而PopupWindow則可以創(chuàng)建一個小的氣泡窗口,將ListView直接塞進去。這些便是下拉選擇框實現(xiàn)的基礎了。

代碼如下:

public class MyPopupwindow extends Activity implements OnClickListener, OnItemClickListener{
private EditText et_input;
private ImageView ib_dropdown;
private ListView listView;
private ArrayList<String> data;
private PopupWindow popupWindow;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_mypopupwindow);

    et_input = (EditText) findViewById(R.id.et_input);
    ib_dropdown = (ImageView) findViewById(R.id.ib_dropdown);
    ib_dropdown.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    if (v.getId() == R.id.ib_dropdown){
        showPopupWindow();
    }
}

private void showPopupWindow() {

    initListView();
    // 顯示下拉選擇框
    popupWindow = new PopupWindow(listView, et_input.getWidth(), 300);
    // 設置點擊外部區(qū)域, 自動隱藏
    popupWindow.setOutsideTouchable(true);// 外部可觸摸
    popupWindow.setBackgroundDrawable(new BitmapDrawable());// 設置空的背景, 響應點擊事件
    popupWindow.setFocusable(true); //設置可獲取焦點
    popupWindow.showAsDropDown(et_input, 0, 0);
}

// 初始化要顯示的內容
private void initListView() {
    listView = new ListView(getApplicationContext());
    listView.setDividerHeight(0);  // 設置分割線邊距
    listView.setBackgroundResource(R.drawable.listview_background);

    listView.setOnItemClickListener(this);

    data = new ArrayList<String>();
    for (int i = 0; i < 30; i ++){
        // 添加數(shù)據(jù)
        String str = "1000" + i;
        data.add(str);
    }

    listView.setAdapter(new MyAdapter());        
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    // listview條目點擊事件
    String string = data.get(position);
    et_input.setText(string);
    popupWindow.dismiss();
}

private class MyAdapter extends BaseAdapter{

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = View.inflate(getApplicationContext(), R.layout.item_number, null);
            viewHolder = new ViewHolder();
            viewHolder.iv_number = (ImageView) view.findViewById(R.id.iv_number);
            viewHolder.tv_number = (TextView) view.findViewById(R.id.tv_number);
            viewHolder.ib_delete = (ImageView) view.findViewById(R.id.ib_delete);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.tv_number.setText(data.get(position));

        viewHolder.ib_delete.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 在條目中點擊刪除
                data.remove(position);
                notifyDataSetChanged();
                if (data.size() == 0) {
                    // 當數(shù)據(jù)為0的時候隱藏popuwindow
                    popupWindow.dismiss();
                }
            }
        });

        return view;
    }

    class ViewHolder{
        ImageView iv_number;
        TextView tv_number;
        ImageView ib_delete;
    }

}

}

我們在點擊EditText右邊的下箭頭時要顯示下拉選擇框,所以創(chuàng)建了一個showPopupWindow()方法,顯示下拉選擇框,而這也是我們的核心代碼所在,代碼如下:

    popupWindow = new PopupWindow(listView, et_input.getWidth(), 300);
    // 設置點擊外部區(qū)域, 自動隱藏
    popupWindow.setOutsideTouchable(true);// 外部可觸摸
    popupWindow.setBackgroundDrawable(new BitmapDrawable());// 設置空的背景, 響應點擊事件
    popupWindow.setFocusable(true); //設置可獲取焦點
    // 將PopupWindow顯示在et_input輸入框之下
    popupWindow.showAsDropDown(et_input, 0, 0);

PopupWindow里面放置了一個listView,同時它的寬度為et_input.getWidth()輸入框的寬度,高度為300。setOutsideTouchable(true)表示外部可觸摸,與setBackgroundDrawable()一起用,那么點擊下拉選擇框外部時,PopupWindow會被隱藏。我們發(fā)現(xiàn)還設置了setFocusable(true)讓popupWindow可獲取焦點,這是因為popupWindow是默認不可獲取焦點的,也就是說它里面的條目是不能夠給點擊的!

接下來就是設置listView的正常流程,需要讀者自己詳細了解了。

但是當一切都設置好了之后,我們選擇listView的其中一條時,發(fā)現(xiàn)仍然不可點擊,但是點擊右側刪除的圖標確實可以的,這又是怎么回事呢?

這是listView被強占了焦點的緣故,因為listView中有ImageButton這種能夠獲取點擊事件的組件存在,所以listView每個條目的點擊事件都集中在了刪除圖標上了。那么怎么樣才能設置listView的其他部分具有點擊事件呢?只需要來listView所在的item布局中添加以下代碼:

    android:descendantFocusability="blocksDescendants"

這時listview中的item中的其他組件都能夠獲取焦點,實現(xiàn)點擊事件

本項目Demo: https://github.com/liaozhoubei/CustomViewDemo

擴展閱讀:

Android 精通自定義視圖(1) http://www.itdecent.cn/p/c2195269ce44

Android 精通自定義視圖(3) http://www.itdecent.cn/p/1660479e76ef

Android 精通自定義視圖(4) http://www.itdecent.cn/p/850e387fc9d8

Android 精通自定義視圖(5) http://www.itdecent.cn/p/93feac19c396

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,781評論 25 709
  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 47,134評論 22 665
  • 一提到符法,自然而然想到道家法術,沒錯,法術是道教千百年來長期賴以助道和濟世的奇功秘術。也是最為眾生熟知的...
    靈濟堂閱讀 4,607評論 0 3
  • 漆黑的天空沒有半點星光,就像是濃墨渲染過的清池一般。 遠遠望去整個夜色一片荒蕪,只有偶爾幾道劃破天際的閃電以及緊隨...
    幻夢邪魂閱讀 541評論 0 2
  • 喚醒49-16 觸動 倔強的母親總是觸動我心里最柔軟的地方 今天在電梯里遇見了住在我家樓下的...
    我和榕樹閱讀 263評論 5 1

友情鏈接更多精彩內容