【Android 應(yīng)用開發(fā)】 自定義組件 寬高適配方法, 手勢監(jiān)聽器操作組件, 回調(diào)接口維護(hù)策略, 繪制方法分析 -- 基于 WheelView 組件分析自定
來源:程序員人生 發(fā)布時間:2014-12-12 08:33:09 閱讀次數(shù):4652次
博客地址 : http://blog.csdn.net/shulianghan/article/details/41520569
代碼下載 :
-- GitHub : https://github.com/han1202012/WheelViewDemo.git
-- CSDN : http://download.csdn.net/detail/han1202012/8208997 ;
博客總結(jié) :
博文內(nèi)容 : 本文完全地分析了 WheelView 所有的源碼, 包括其適配器類型, 兩種回調(diào)接口 (選中條目改變回調(diào), 和開始結(jié)束轉(zhuǎn)動回調(diào)), 和詳細(xì)的分析了 WheelView 主題源碼, 其中 組件寬高丈量, 手勢監(jiān)聽器添加, 和精準(zhǔn)的繪圖方法是主要目的, 花了將近1周時間, 感覺很值, 在這里分享給大家;
WheelView 使用方法 : 創(chuàng)建 WheelView 組件 --> 設(shè)置顯示條目數(shù) --> 設(shè)置循環(huán) --> 設(shè)置適配器 --> 設(shè)置監(jiān)聽器 ;
自定義組件寬高獲得策略 : MeasureSpec 最大模式 取 默許值 和 給定值中較小的那個, 未定義模式取默許值, 精準(zhǔn)模式取 給定值;
自定義組件保護(hù)各種回調(diào)監(jiān)聽器策略 : 保護(hù)集合, 將監(jiān)聽器置于集合中, 回調(diào)接口時遍歷集合元素, 回調(diào)每一個元素的接口方法;
自定義組件手勢監(jiān)聽器添加方法 : 創(chuàng)建手勢監(jiān)聽器, 將手勢監(jiān)聽器傳入手勢探測器, 在 onTouchEvent() 方法中回調(diào)手勢監(jiān)聽器的 onTouchEvent()方法;
1. WheelView 簡介
1. WheelView 效果
在 Android 中實現(xiàn)類似與 IOS 的 WheelView 控件 : 如圖

2. WheelView 使用流程
(1) 基本流程簡介
獲得組件 --> 設(shè)置顯示條目數(shù) --> 設(shè)置循環(huán) --> 設(shè)置適配器 --> 設(shè)置條目改變監(jiān)聽器 --> 設(shè)置轉(zhuǎn)動監(jiān)聽器
a. 創(chuàng)建 WheelView 組件 : 使用 構(gòu)造方法 或 從布局文件獲得 WheelView 組件;
b. 設(shè)置顯示條目數(shù) : 調(diào)用 WheelView 組件對象的 setVisibleItems 方法 設(shè)置;
c. 設(shè)置是不是循環(huán) : 設(shè)置 WheelView 是不是循環(huán), 調(diào)用 setCyclic() 方法設(shè)置;
d. 設(shè)置適配器 : 調(diào)用 WheelView 組件的 setAdapter() 方法設(shè)置;
e. 設(shè)置條目改變監(jiān)聽器 : 調(diào)用 WheelView 組件對象的 addChangingListener() 方法設(shè)置;
f. 設(shè)置轉(zhuǎn)動監(jiān)聽器 : 調(diào)用 WheelView 組件對象的 addScrollingListener() 方法設(shè)置;
(2) 代碼實例
a. 創(chuàng)建 WheelView 對象 :
//創(chuàng)建 WheelView 組件
final WheelView wheelLeft = new WheelView(context);
b. 設(shè)置 WheelView 顯示條目數(shù) :
//設(shè)置 WheelView 組件最多顯示 5 個元素
wheelLeft.setVisibleItems(5);
c. 設(shè)置 WheelView 是不是轉(zhuǎn)動循環(huán) :
//設(shè)置 WheelView 元素是不是循環(huán)轉(zhuǎn)動
wheelLeft.setCyclic(false);
d. 設(shè)置 WheelView 適配器 :
//設(shè)置 WheelView 適配器
wheelLeft.setAdapter(new ArrayWheelAdapter<String>(left));
e. 設(shè)置條目改變監(jiān)聽器 :
//為左邊的 WheelView 設(shè)置條目改變監(jiān)聽器
wheelLeft.addChangingListener(new OnWheelChangedListener() {
@Override
public void onChanged(WheelView wheel, int oldValue, int newValue) {
//設(shè)置右邊的 WheelView 的適配器
wheelRight.setAdapter(new ArrayWheelAdapter<String>(right[newValue]));
wheelRight.setCurrentItem(right[newValue].length / 2);
}
});
f. 設(shè)置轉(zhuǎn)動監(jiān)聽器 :
wheelLeft.addScrollingListener(new OnWheelScrollListener() {
@Override
public void onScrollingStarted(WheelView wheel) {
// TODO Auto-generated method stub
}
@Override
public void onScrollingFinished(WheelView wheel) {
// TODO Auto-generated method stub
}
});
2. WheelView 適配器 監(jiān)聽器 相干接口分析
1. 適配器 分析
這里定義了1個適配器接口, 和兩個適配器類, 1個用于任意類型的數(shù)據(jù)集適配, 1個用于數(shù)字適配;
適配器操作 : 在 WheelView.java 中通過 setAdapter(WheelAdapter adapter) 和 getAdapter() 方法設(shè)置 獲得 適配器;
-- 適配器經(jīng)常使用操作 : 在 WheelView 中定義了 getItem(), getItemsCount(), getMaxmiumLength() 方法獲得 適配器的相干信息;
/**
* 獲得該 WheelView 的適配器
*
* @return
* 返回適配器
*/
public WheelAdapter getAdapter() {
return adapter;
}
/**
* 設(shè)置適配器
*
* @param adapter
* 要設(shè)置的適配器
*/
public void setAdapter(WheelAdapter adapter) {
this.adapter = adapter;
invalidateLayouts();
invalidate();
}
(1) 適配器接口 ( interface WheelAdapter )
適配器接口 : WheelAdapter;
-- 接口作用 : 該接口是所有適配器的接口, 適配器類都需要實現(xiàn)該接口;
接口抽象方法介紹 :
-- getItemsCount() : 獲得適配器數(shù)據(jù)集合中元素個數(shù);
/**
* 獲得條目的個數(shù)
*
* @return
* WheelView 的條目個數(shù)
*/
public int getItemsCount();
-- getItem(int index) : 獲得適配器集合的中指定索引元素;
/**
* 根據(jù)索引位置獲得 WheelView 的條目
*
* @param index
* 條目的索引
* @return
* WheelView 上顯示的條目的值
*/
public String getItem(int index);
-- getMaximumLength() : 獲得 WheelView 在界面上的顯示寬度;
/**
* 獲得條目的最大長度. 用來定義 WheelView 的寬度. 如果返回 ⑴, 就會使用默許寬度
*
* @return
* 條目的最大寬度 或 ⑴
*/
public int getMaximumLength();
(2) 數(shù)組適配器 ( class ArrayWheelAdapter<T> implements WheelAdapter )
適配器作用 : 該適配器可以傳入任何數(shù)據(jù)類型的數(shù)組, 可以是 字符串?dāng)?shù)組, 也能夠是任何對象的數(shù)組, 傳入的數(shù)組作為適配器的數(shù)據(jù)源;
成員變量分析 :
-- 數(shù)據(jù)源 :
/** 適配器的數(shù)據(jù)源 */
private T items[];
-- WheelView 最大寬度 :
/** WheelView 的寬度 */
private int length;
構(gòu)造方法分析 :
-- ArrayWheelAdapter(T items[], int length) : 傳入 T 類型 對象數(shù)組, 和 WheelView 的寬度;
/**
* 構(gòu)造方法
*
* @param items
* 適配器數(shù)據(jù)源 集合 T 類型的數(shù)組
* @param length
* 適配器數(shù)據(jù)源 集合 T 數(shù)組長度
*/
public ArrayWheelAdapter(T items[], int length) {
this.items = items;
this.length = length;
}
-- ArrayWheelAdapter(T items[]) : 傳入 T 類型對象數(shù)組, 寬度使用默許的寬度;
/**
* 構(gòu)造方法
*
* @param items
* 適配器數(shù)據(jù)源集合 T 類型數(shù)組
*/
public ArrayWheelAdapter(T items[]) {
this(items, DEFAULT_LENGTH);
}
實現(xiàn)的父類方法分析 :
-- getItem(int index) : 根據(jù)索引獲得數(shù)組中對應(yīng)位置的對象的字符串類型;
@Override
public String getItem(int index) {
//如果這個索引值合法, 就返回 item 數(shù)組對應(yīng)的元素的字符串情勢
if (index >= 0 && index < items.length) {
return items[index].toString();
}
return null;
}
-- getItemsCount() : 獲得數(shù)據(jù)集廣大小, 直接返回數(shù)組大小;
@Override
public int getItemsCount() {
//返回 item 數(shù)組的長度
return items.length;
}
-- getMaximumLength() : 獲得 WheelView 的最大寬度;
@Override
public int getMaximumLength() {
//返回 item 元素的寬度
return length;
}
(3) 數(shù)字適配器 ( class NumericWheelAdapter implements WheelAdapter )
NumericWheelAdapter 適配器作用 : 數(shù)字作為 WheelView 適配器的顯示值;
成員變量分析 :
-- 最小值 : WheelView 數(shù)值顯示的最小值;
/** 設(shè)置的最小值 */
private int minValue;
-- 最大值 : WheelView 數(shù)值顯示的最大值;
/** 設(shè)置的最大值 */
private int maxValue;
--
格式化字符串 : 用于字符串的格式化;
/** 格式化字符串, 用于格式化 貨幣, 科學(xué)計數(shù), 106進(jìn)制 等格式 */
private String format;
構(gòu)造方法分析 :
-- NumericWheelAdapter() : 默許的構(gòu)造方法, 使用默許的最大最小值;
/**
* 默許的構(gòu)造方法, 使用默許的最大最小值
*/
public NumericWheelAdapter() {
this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
}
-- NumericWheelAdapter(int minValue, int maxValue) : 傳入1個最大最小值;
/**
* 構(gòu)造方法
*
* @param minValue
* 最小值
* @param maxValue
* 最大值
*/
public NumericWheelAdapter(int minValue, int maxValue) {
this(minValue, maxValue, null);
}
-- NumericWheelAdapter(int minValue, int maxValue, String format) : 傳入最大最小值, 和數(shù)字格式化方式;
/**
* 構(gòu)造方法
*
* @param minValue
* 最小值
* @param maxValue
* 最大值
* @param format
* 格式化字符串
*/
public NumericWheelAdapter(int minValue, int maxValue, String format) {
this.minValue = minValue;
this.maxValue = maxValue;
this.format = format;
}
實現(xiàn)的父類方法 :
-- 獲得條目 : 如果需要格式化, 先進(jìn)行格式化;
@Override
public String getItem(int index) {
String result = "";
if (index >= 0 && index < getItemsCount()) {
int value = minValue + index;
//如果 format 不為 null, 那末格式化字符串, 如果為 null, 直接返回數(shù)字
if(format != null){
result = String.format(format, value);
}else{
result = Integer.toString(value);
}
return result;
}
return null;
}
-- 獲得元素個數(shù) :
@Override
public int getItemsCount() {
//返回數(shù)字總個數(shù)
return maxValue - minValue + 1;
}
-- 獲得 WheelView 最大寬度 :
@Override
public int getMaximumLength() {
//獲得 最大值 和 最小值 中的 較大的數(shù)字
int max = Math.max(Math.abs(maxValue), Math.abs(minValue));
//獲得這個數(shù)字 的 字符串情勢的 字符串長度
int maxLen = Integer.toString(max).length();
if (minValue < 0) {
maxLen++;
}
return maxLen;
}
2. 監(jiān)聽器相干接口
(1) 條目改變監(jiān)聽器 ( interface OnWheelChangedListener )
監(jiān)聽器作用 : 在 WheelView 條目改變的時候, 回調(diào)該監(jiān)聽器的接口方法, 履行條目改變對應(yīng)的操作;
接口方法介紹 :
-- onChanged(WheelView wheel, int oldValue, int newValue) : 傳入 WheelView 組件對象, 和 舊的 和 新的 條目值索引;
/**
* 當(dāng)前條目改變時回調(diào)該方法
*
* @param wheel
* 條目改變的 WheelView 對象
* @param oldValue
* WheelView 舊的條目值
* @param newValue
* WheelView 新的條目值
*/
void onChanged(WheelView wheel, int oldValue, int newValue);
(2) 轉(zhuǎn)動監(jiān)聽器 ( interface OnWheelScrollListener )
轉(zhuǎn)動監(jiān)聽器作用 : 在 WheelView 轉(zhuǎn)動動作 開始 和 結(jié)束的時候回調(diào)對應(yīng)的方法, 在對應(yīng)方法中進(jìn)行相應(yīng)的操作;
接口方法介紹 :
-- 開始轉(zhuǎn)動方法 : 在轉(zhuǎn)動開始的時候回調(diào)該方法;
/**
* 在 WheelView 轉(zhuǎn)動開始的時候回調(diào)該接口
*
* @param wheel
* 開始轉(zhuǎn)動的 WheelView 對象
*/
void onScrollingStarted(WheelView wheel);
-- 停止轉(zhuǎn)動方法 : 在轉(zhuǎn)動結(jié)束的時候回調(diào)該方法;
/**
* 在 WheelView 轉(zhuǎn)動結(jié)束的時候回調(diào)該接口
*
* @param wheel
* 結(jié)束轉(zhuǎn)動的 WheelView 對象
*/
void onScrollingFinished(WheelView wheel);
3. WheelView 解析
1. 觸摸 點擊 手勢 動作操作控制組件 模塊
(1) 創(chuàng)建手勢監(jiān)聽器
手勢監(jiān)聽器創(chuàng)建及對應(yīng)方法 :
-- onDown(MotionEvent e) : 在按下的時候回調(diào)該方法, e 參數(shù)是按下的事件;
-- onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) : 轉(zhuǎn)動的時候回調(diào)該方法, e1 轉(zhuǎn)動第1次按下事件, e2 當(dāng)前轉(zhuǎn)動的觸摸事件, X 上1次轉(zhuǎn)動到這1次轉(zhuǎn)動 x 軸距離, Y 上1次轉(zhuǎn)動到這1次轉(zhuǎn)動 y 軸距離;
-- onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) : 快速急沖轉(zhuǎn)動時回調(diào)的方法, e1 e2 與上面參數(shù)相同, velocityX 是手勢在 x 軸的速度, velocityY 是手勢在 y 軸的速度;
-- 代碼示例 :
/*
* 手勢監(jiān)聽器監(jiān)聽到 轉(zhuǎn)動操作后回調(diào)
*
* 參數(shù)解析 :
* MotionEvent e1 : 觸發(fā)轉(zhuǎn)動時第1次按下的事件
* MotionEvent e2 : 觸發(fā)當(dāng)前轉(zhuǎn)動的移動事件
* float distanceX : 自從上1次調(diào)用 該方法 到這1次 x 軸轉(zhuǎn)動的距離,
* 注意不是 e1 到 e2 的距離, e1 到 e2 的距離是從開始轉(zhuǎn)動到現(xiàn)在的轉(zhuǎn)動距離
* float distanceY : 自從上1次回調(diào)該方法到這1次 y 軸轉(zhuǎn)動的距離
*
* 返回值 : 如果事件成功觸發(fā), 履行完了方法中的操作, 返回true, 否則返回 false
* (non-Javadoc)
* @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent, android.view.MotionEvent, float, float)
*/
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//開始轉(zhuǎn)動, 并回調(diào)轉(zhuǎn)動監(jiān)聽器集合中監(jiān)聽器的 開始轉(zhuǎn)動方法
startScrolling();
doScroll((int) -distanceY);
return true;
}
/*
* 當(dāng)1個急沖手勢產(chǎn)生后 回調(diào)該方法, 會計算出該手勢在 x 軸 y 軸的速率
*
* 參數(shù)解析 :
* -- MotionEvent e1 : 急沖動作的第1次觸摸事件;
* -- MotionEvent e2 : 急沖動作的移動產(chǎn)生的時候的觸摸事件;
* -- float velocityX : x 軸的速率
* -- float velocityY : y 軸的速率
*
* 返回值 : 如果履行終了返回 true, 否則返回false, 這個就是自己定義的
*
* (non-Javadoc)
* @see android.view.GestureDetector.SimpleOnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float)
*/
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//計算上1次的 y 軸位置, 當(dāng)前的條目高度 加上 剩余的 不夠1行高度的那部份
lastScrollY = currentItem * getItemHeight() + scrollingOffset;
//如果可以循環(huán)最大值是無窮大, 不能循環(huán)就是條目數(shù)的高度值
int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
int minY = isCyclic ? -maxY : 0;
/*
* Scroll 開始根據(jù)1個急沖手勢轉(zhuǎn)動, 轉(zhuǎn)動的距離與初速度有關(guān)
* 參數(shù)介紹 :
* -- int startX : 開始時的 X軸位置
* -- int startY : 開始時的 y軸位置
* -- int velocityX : 急沖手勢的 x 軸的初速度, 單位 px/s
* -- int velocityY : 急沖手勢的 y 軸的初速度, 單位 px/s
* -- int minX : x 軸轉(zhuǎn)動的最小值
* -- int maxX : x 軸轉(zhuǎn)動的最大值
* -- int minY : y 軸轉(zhuǎn)動的最小值
* -- int maxY : y 軸轉(zhuǎn)動的最大值
*/
scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
setNextMessage(MESSAGE_SCROLL);
return true;
}
};
(2) 創(chuàng)建手勢探測器
手勢探測器創(chuàng)建 : 調(diào)用 其構(gòu)造函數(shù), 傳入 上下文對象 和 手勢監(jiān)聽器對象;
-- 制止長按操作 : 調(diào)用 setIsLongpressEnabled(false) 方法, 制止長按操作, 由于 長按操作會屏蔽轉(zhuǎn)動事件;
//創(chuàng)建1個手勢處理
gestureDetector = new GestureDetector(context, gestureListener);
/*
* 是不是允許長按操作,
* 如果設(shè)置為 true 用戶按下不松開, 會返回1個長按事件,
* 如果設(shè)置為 false, 按下不松開滑動的話 會收到轉(zhuǎn)動事件.
*/
gestureDetector.setIsLongpressEnabled(false);
(3) 將手勢探測器 與 組件結(jié)合
關(guān)聯(lián)手勢探測器 與 組件 : 在組件的 onTouchEvent(MotionEvent event) 方法中, 調(diào)用手勢探測器的 gestureDetector.onTouchEvent(event) 方法便可;
/*
* 繼承自 View 的觸摸事件, 當(dāng)出現(xiàn)觸摸事件的時候, 就會回調(diào)該方法
* (non-Javadoc)
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲得適配器
WheelAdapter adapter = getAdapter();
if (adapter == null) {
return true;
}
/*
* gestureDetector.onTouchEvent(event) : 分析給定的動作, 如果可用, 調(diào)用 手勢檢測器的 onTouchEvent 方法
* -- 參數(shù)解析 : ev , 觸摸事件
* -- 返回值 : 如果手勢監(jiān)聽器成功履行了該方法, 返回true, 如果履行出現(xiàn)意外 返回 false;
*/
if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
justify();
}
return true;
}
2. Scroller 簡介
(1) Scroller 簡介
Scroller 通用作用 : Scroller 組件其實不是1個布局組件, 該組件是運行在后臺的, 通過1些方法設(shè)定 Scroller 對象 的操作 或 動畫, 然后讓 Scroller 運行在后臺中 用于摹擬轉(zhuǎn)動操作, 在適當(dāng)?shù)臅r機 獲得該對象的坐標(biāo)信息, 這些信息是在后臺運算出來的;
Scroller 在本 View 中作用 : Android 的這個自定義的 WheelView 組件, 可以平滑的轉(zhuǎn)動, 當(dāng)我們做1個加速滑動時, 會根據(jù)速度計算出滑動的距離, 這些數(shù)據(jù)都是在 Scroller 中計算出來的;
(2) 設(shè)定 Scroller 對象的動作參數(shù)
終止轉(zhuǎn)動 :
-- 終止轉(zhuǎn)動 跳轉(zhuǎn)到目標(biāo)位置 : 終止平緩的動畫, 直接跳轉(zhuǎn)到終究的 x y 軸的坐標(biāo)位置;
public void abortAnimation()
-- 終止轉(zhuǎn)動 停止在當(dāng)前位置 : 強行結(jié)束 Scroll 的轉(zhuǎn)動;
public final void forceFinished(boolean finished)
設(shè)置轉(zhuǎn)動參數(shù) :
-- 設(shè)置終究 x 軸坐標(biāo) :
public void setFinalX(int newX)
-- 設(shè)置終究 y 軸坐標(biāo) :
public void setFinalY(int newY)
-- 設(shè)置轉(zhuǎn)動磨擦力 :
public final void setFriction(float friction)
設(shè)置動作 :
-- 開始轉(zhuǎn)動 : 傳入?yún)?shù) 開始 x 位置, 開始 y 位置, x 軸轉(zhuǎn)動距離, y 軸轉(zhuǎn)動距離;
public void startScroll(int startX, int startY, int dx, int dy)
--
開始轉(zhuǎn)動 設(shè)定時間 : 最后1個參數(shù)是時間, 單位是 ms;
public void startScroll(int startX, int startY, int dx, int dy, int duration)
--
急沖轉(zhuǎn)動 : 根據(jù)1個 急沖 手勢進(jìn)行轉(zhuǎn)動, 傳入?yún)?shù) : x軸開始位置, y軸開始位置, x 軸速度, y 軸速度, x 軸最小速度, x 軸最大速度, y 軸最小速度, y 軸最大速度;
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY)
延長轉(zhuǎn)動時間 : 延長轉(zhuǎn)動的時間, 讓轉(zhuǎn)動滾的更遠(yuǎn)1些;
public void extendDuration(int extend)
(3) 獲得 Scroll 后臺運行參數(shù)
獲得當(dāng)前數(shù)據(jù) :
-- 獲得當(dāng)前 x 軸坐標(biāo) :
public final int getCurrX()
-- 獲得當(dāng)前 y 軸坐標(biāo) :
public final int getCurrY()
-- 獲得當(dāng)前速度 :
public float getCurrVelocity()
獲得開始結(jié)束時的數(shù)據(jù) :
-- 獲得開始 x 軸坐標(biāo) :
public final int getStartX()
-- 獲得開始 y 軸坐標(biāo) :
public final int getStartY()
-- 獲得終究 x 軸坐標(biāo) : 該參數(shù)只在急沖轉(zhuǎn)動時有效;
public final int getFinalX()
-- 獲得終究 y 軸坐標(biāo) : 該參數(shù)只在急沖轉(zhuǎn)動時有效;
public final int getFinalY()
查看是不是轉(zhuǎn)動終了 :
public final boolean isFinished()
獲得從開始轉(zhuǎn)動到現(xiàn)在的時間 :
public int timePassed()
獲得新位置 : 調(diào)用該方法可以獲得新位置, 如果返回 true 說明動畫還沒履行終了;
public boolean computeScrollOffset()
(4) Scroll 在 WheelView 中的應(yīng)用
Scroller 創(chuàng)建 :
//使用默許的 時間 和 插入器 創(chuàng)建1個轉(zhuǎn)動器
scroller = new Scroller(context);
手勢監(jiān)聽器 SimpleOnGestureListener 對象中的 onDown() 方法 : 如果轉(zhuǎn)動還在履行, 那末強行停止 Scroller 轉(zhuǎn)動;
//按下操作
public boolean onDown(MotionEvent e) {
//如果轉(zhuǎn)動在履行
if (isScrollingPerformed) {
//轉(zhuǎn)動強迫停止, 按下的時候不能繼續(xù)轉(zhuǎn)動
scroller.forceFinished(true);
//清算信息
clearMessages();
return true;
}
return false;
}
當(dāng)手勢監(jiān)聽器 SimpleOnGestureListener 對象中有急沖動作時 onFling() 方法中 : 手勢監(jiān)聽器監(jiān)聽到了 急沖動作, 那末 Scroller 也進(jìn)行對應(yīng)操作;
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//計算上1次的 y 軸位置, 當(dāng)前的條目高度 加上 剩余的 不夠1行高度的那部份
lastScrollY = currentItem * getItemHeight() + scrollingOffset;
//如果可以循環(huán)最大值是無窮大, 不能循環(huán)就是條目數(shù)的高度值
int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
int minY = isCyclic ? -maxY : 0;
/*
* Scroll 開始根據(jù)1個急沖手勢轉(zhuǎn)動, 轉(zhuǎn)動的距離與初速度有關(guān)
* 參數(shù)介紹 :
* -- int startX : 開始時的 X軸位置
* -- int startY : 開始時的 y軸位置
* -- int velocityX : 急沖手勢的 x 軸的初速度, 單位 px/s
* -- int velocityY : 急沖手勢的 y 軸的初速度, 單位 px/s
* -- int minX : x 軸轉(zhuǎn)動的最小值
* -- int maxX : x 軸轉(zhuǎn)動的最大值
* -- int minY : y 軸轉(zhuǎn)動的最小值
* -- int maxY : y 軸轉(zhuǎn)動的最大值
*/
scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
setNextMessage(MESSAGE_SCROLL);
return true;
}
動畫控制 Handler 中 :
-- 轉(zhuǎn)動 : 獲得當(dāng)前 Scroller 的 y 軸位置, 與上1次的 y 軸位置對照, 如果 間距 delta 不為0, 就轉(zhuǎn)動;
-- 查看是不是停止 : 如果現(xiàn)在距離 到 終究距離 小于最小轉(zhuǎn)動距離, 強迫停止;
-- 履行 msg.what 指令 : 如果需要停止, 強迫停止, 否則調(diào)劑坐標(biāo);
/**
* 動畫控制器
* animation handler
*
* 可能會造成內(nèi)存泄漏 : 添加注解 HandlerLeak
* Handler 類應(yīng)當(dāng)應(yīng)當(dāng)為static類型,否則有可能造成泄漏。
* 在程序消息隊列中排隊的消息保持了對目標(biāo)Handler類的利用。
* 如果Handler是個內(nèi)部類,那 么它也會保持它所在的外部類的援用。
* 為了不泄漏這個外部類,應(yīng)當(dāng)將Handler聲明為static嵌套類,并且使用對外部類的弱利用。
*/
@SuppressLint("HandlerLeak")
private Handler animationHandler = new Handler() {
public void handleMessage(Message msg) {
//回調(diào)該方法獲得當(dāng)前位置, 如果返回true, 說明動畫還沒有履行終了
scroller.computeScrollOffset();
//獲得當(dāng)前 y 位置
int currY = scroller.getCurrY();
//獲得已轉(zhuǎn)動了的位置, 使用上1次位置 減去 當(dāng)前位置
int delta = lastScrollY - currY;
lastScrollY = currY;
if (delta != 0) {
//改變值不為 0 , 繼續(xù)轉(zhuǎn)動
doScroll(delta);
}
/*
* 如果轉(zhuǎn)動到了指定的位置, 轉(zhuǎn)動還沒有停止
* 這時候需要強迫停止
*/
if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {
currY = scroller.getFinalY();
scroller.forceFinished(true);
}
/*
* 如果轉(zhuǎn)動沒有停止
* 再向 Handler 發(fā)送1個停止
*/
if (!scroller.isFinished()) {
animationHandler.sendEmptyMessage(msg.what);
} else if (msg.what == MESSAGE_SCROLL) {
justify();
} else {
finishScrolling();
}
}
};
3. StaticLayout 布局容器
(1) StaticLayout 解析
StaticLayout 解析 : 該組件用于顯示文本, 1旦該文本被顯示后, 就不能再編輯, 如果想要修改文本, 使用 DynamicLayout 布局便可;
-- 使用處景 : 1般情況下不會使用該組件, 當(dāng)想要自定義組件 或 想要使用 Canvas 繪制文本時 才使用該布局;
經(jīng)常使用方法解析 :
-- 獲得底部 Padding : 獲得底部 到最后1行文字的 間隔, 單位是 px;
public int getBottomPadding()
-- 獲得頂部 Padding :
public int getTopPadding()
--
獲得省略個數(shù) : 獲得某1行需要省略的字符個數(shù);
public int getEllipsisCount(int line)
--
獲得省略開始位置 : 獲得某1行要省略的字符串的第1個位置索引;
public int getEllipsisStart(int line)
--
獲得省略的寬度 : 獲得某1行省略字符串的寬度, 單位 px;
public int getEllipsisStart(int line)
--
獲得是不是處理特殊符號 :
public boolean getLineContainsTab(int line)
--
獲得文字的行數(shù) :
public int getLineCount()
--
獲得頂部位置 : 獲得某1行頂部的位置;
public int getLineTop(int line)
--
獲得某1行底部位置 :
public int getLineDescent(int line)
--
獲得行的方向 : 字符串從左至右 還是從右至左;
public final Directions getLineDirections(int line)
--
獲得某行第1個字符索引 : 獲得的是 某1行 第1個字符 在全部字符串的索引;
public int getLineStart(int line)
--
獲得該行段落方向 : 獲得該行文字方向, 左至右 或 右至左;
public int getParagraphDirection(int line)
--
獲得某個垂直位置顯示的行數(shù) :
public int getLineForVertical(int vertical)
(2) 布局顯示
布局創(chuàng)建 :
-- 3種布局 : WheelView 中觸及到了3種 StaticLayout 布局, 普通條目布局 itemLayout, 選中條目布局 valueLayout, 標(biāo)簽布局 labelLayout;
-- 創(chuàng)建時機 : 在 View 組件 每次 onMeasure() 和 onDraw() 方法中都要重新創(chuàng)建對應(yīng)布局;
-- 創(chuàng)建布局源碼 :
/**
* 創(chuàng)建布局
*
* @param widthItems
* 布局條目寬度
* @param widthLabel
* label 寬度
*/
private void createLayouts(int widthItems, int widthLabel) {
/*
* 創(chuàng)建普通條目布局
* 如果 普通條目布局 為 null 或 普通條目布局的寬度 大于 傳入的寬度, 這時候需要重新創(chuàng)建布局
* 如果 普通條目布局存在, 并且其寬度小于傳入的寬度, 此時需要將
*/
if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {
/*
* android.text.StaticLayout.StaticLayout(
* CharSequence source, TextPaint paint,
* int width, Alignment align,
* float spacingmult, float spacingadd, boolean includepad)
* 傳入?yún)?shù)介紹 :
* CharSequence source : 需要分行顯示的字符串
* TextPaint paint : 繪制字符串的畫筆
* int width : 條目的寬度
* Alignment align : Layout 的對齊方式, ALIGN_CENTER 居中對齊, ALIGN_NORMAL 左對齊, Alignment.ALIGN_OPPOSITE 右對齊
* float spacingmult : 行間距, 1.5f 代表 1.5 倍字體高度
* float spacingadd : 基礎(chǔ)行距上增加多少 , 真實行間距 等于 spacingmult 和 spacingadd 的和
* boolean includepad :
*/
itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,
widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,
ADDITIONAL_ITEM_HEIGHT, false);
} else {
//調(diào)用 Layout 內(nèi)置的方法 increaseWidthTo 將寬度提升到指定的寬度
itemsLayout.increaseWidthTo(widthItems);
}
/*
* 創(chuàng)建選中條目
*/
if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {
String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;
valueLayout = new StaticLayout(text != null ? text : "", valuePaint, widthItems,
widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,
ADDITIONAL_ITEM_HEIGHT, false);
} else if (isScrollingPerformed) {
valueLayout = null;
} else {
valueLayout.increaseWidthTo(widthItems);
}
/*
* 創(chuàng)建標(biāo)簽條目
*/
if (widthLabel > 0) {
if (labelLayout == null || labelLayout.getWidth() > widthLabel) {
labelLayout = new StaticLayout(label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,
ADDITIONAL_ITEM_HEIGHT, false);
} else {
labelLayout.increaseWidthTo(widthLabel);
}
}
}
4. 監(jiān)聽器管理
監(jiān)聽器集合保護(hù) :
-- 定義監(jiān)聽器集合 : 在 View 組件中 定義1個 List 集合, 集合中寄存 監(jiān)聽器元素;
/** 條目改變監(jiān)聽器集合 封裝了條目改變方法, 當(dāng)條目改變時回調(diào) */
private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();
/** 條目轉(zhuǎn)動監(jiān)聽器集合, 該監(jiān)聽器封裝了 開始轉(zhuǎn)動方法, 結(jié)束轉(zhuǎn)動方法 */
private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();
-- 提供對監(jiān)聽器集合的添加刪除接口 : 提供 對集合 進(jìn)行 添加 和 刪除的接口;
/**
* 添加 WheelView 選擇的元素改變監(jiān)聽器
*
* @param listener
* the listener
*/
public void addChangingListener(OnWheelChangedListener listener) {
changingListeners.add(listener);
}
/**
* 移除 WheelView 元素改變監(jiān)聽器
*
* @param listener
* the listener
*/
public void removeChangingListener(OnWheelChangedListener listener) {
changingListeners.remove(listener);
}
--
調(diào)用監(jiān)聽器接口 :
/**
* 回調(diào)元素改變監(jiān)聽器集合的元素改變監(jiān)聽器元素的元素改變方法
*
* @param oldValue
* 舊的 WheelView選中的值
* @param newValue
* 新的 WheelView選中的值
*/
protected void notifyChangingListeners(int oldValue, int newValue) {
for (OnWheelChangedListener listener : changingListeners) {
listener.onChanged(this, oldValue, newValue);
}
}
5. 自定義 View 對象的寬高
(1) onMeasure 方法 MeasureSpec 模式解析
常規(guī)處理方法 : 組件的寬高有3種情況, widthMeasureSpec 有3種模式 最大模式, 精準(zhǔn)模式, 未定義模式;
-- 最大模式 : 在 組件的寬或高 warp_content 屬性時, 會使用最大模式;
-- 精準(zhǔn)模式 : 當(dāng)給組件寬 或高 定義1個值 或 使用 match_parent 時, 會使用精準(zhǔn)模式;
處理寬高的常規(guī)代碼 :
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲得寬度 和 高度的模式 和 大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Log.i(TAG, "寬度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "
"
+ "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);
int width = 0;
int height = 0;
/*
* 精準(zhǔn)模式
* 精準(zhǔn)模式下 高度就是精確的高度
*/
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
//未定義模式 和 最大模式
} else {
//未定義模式下 獲得布局需要的高度
height = 100;
//最大模式下 獲得 布局高度 和 布局所需高度的最小值
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = 100;
if (heightMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
Log.i(TAG, "終究結(jié)果 : 寬度 : " + width + " , 高度 : " + height);
setMeasuredDimension(width, height);
}
public String getMode(int mode) {
String modeName = "";
if(mode == MeasureSpec.EXACTLY){
modeName = "精準(zhǔn)模式";
}else if(mode == MeasureSpec.AT_MOST){
modeName = "最大模式";
}else if(mode == MeasureSpec.UNSPECIFIED){
modeName = "未定義模式";
}
return modeName;
}
(2) 測試上述代碼
使用下面的自定義組件測試 :
package cn.org.octopus.wheelview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class MyView extends View {
public static final String TAG = "octopus.my.view";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲得寬度 和 高度的模式 和 大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Log.i(TAG, "寬度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "
"
+ "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);
int width = 0;
int height = 0;
/*
* 精準(zhǔn)模式
* 精準(zhǔn)模式下 高度就是精確的高度
*/
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
//未定義模式 和 最大模式
} else {
//未定義模式下 獲得布局需要的高度
height = 100;
//最大模式下 獲得 布局高度 和 布局所需高度的最小值
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = 100;
if (heightMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
Log.i(TAG, "終究結(jié)果 : 寬度 : " + width + " , 高度 : " + height);
setMeasuredDimension(width, height);
}
public String getMode(int mode) {
String modeName = "";
if(mode == MeasureSpec.EXACTLY){
modeName = "精準(zhǔn)模式";
}else if(mode
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機掃描二維碼進(jìn)行捐贈
------分隔線----------------------------
------分隔線----------------------------