前言
這是新開(kāi)的博客第一篇文章攘乒。這一篇針對(duì)的是自定義控件盼砍。在github上有一個(gè)自定義控件的效果如下:
這個(gè)水平方向上無(wú)限滾動(dòng)的控件篮灼,可以用來(lái)制作自定義進(jìn)度條生年,或者一些tab效果。
具體使用方法請(qǐng)移步github晋辆。
它實(shí)現(xiàn)該效果只有50行代碼不到渠脉,所以寫(xiě)這篇博客來(lái)記錄該控件的實(shí)現(xiàn)過(guò)程。
1. 分析需求
- 設(shè)置進(jìn)去的圖片可以水平滾動(dòng)瓶佳;
- 滾動(dòng)展示的圖片可以自行設(shè)置芋膘,滾動(dòng)的速度也可以自行設(shè)置,霸饲;
- 速度為正为朋,向后滾動(dòng)(從右向左滾動(dòng));為負(fù)時(shí)厚脉,向前滾動(dòng)(從左向右滾動(dòng))习寸;
2. xml資源相關(guān)
能夠自行設(shè)置,那么需要去設(shè)置自定義屬性來(lái)控制速度及滾動(dòng)展示的圖片傻工。
在res/values下創(chuàng)建attrs.xml霞溪,并采用以下方式定義自定義屬性:
<resource>
<declare-styleable name="ScrollingView">
<attr name="speed" format="dimension" />
<attr name="src" format="reference" />
</declare-styleable>
</resource>
speed為速度孵滞,src為滾動(dòng)展示的圖片資源。
在layout布局文件中使用自定義屬性鸯匹,首先在根布局view中設(shè)置命名空間:
xmlns:app="http://schemas.android.com/apk/res-auto"
然后在自定義控件中設(shè)置自定義屬性
3. java代碼
3.1 初始化
創(chuàng)建一個(gè)View類(lèi)坊饶,并在構(gòu)造方法中獲得自定義屬性speed和圖片。凡是在xml中定義的控件殴蓬,會(huì)調(diào)用以下形式的構(gòu)造方法:
public ScrollingView(Context context, AttributeSet attrs) {
super(context, attrs);
try{
speed = typedArray.getDimensionPixelSize(R.styleable.ScrollingView_speed, 1);
bitmap = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.ScrollingView_src, 0));
}finally {
typedArray.recycle();
}
}
3.2 onMeasure測(cè)量
需要設(shè)置控件的寬高匿级,不然會(huì)出現(xiàn)高度顯示不正常。這里采用的方式為:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bitmap.getHeight());
}
3.3 onDraw繪制
關(guān)鍵點(diǎn)來(lái)了染厅,該控件最大的難點(diǎn)就在繪制痘绎,我將繪制邏輯分為三個(gè)要點(diǎn):
3.3.1 要點(diǎn)一
如果當(dāng)前的控件寬度大于要滾動(dòng)的圖片寬度,那么會(huì)出現(xiàn)空白肖粮,應(yīng)該需要重復(fù)繪制多個(gè)圖片來(lái)填充孤页。
- 解決方案:用一個(gè)變量left記錄當(dāng)前canvas繪制bitmap時(shí)的左方基坐標(biāo),如果left小于控件的寬度尿赚,就繼續(xù)繪制散庶,每繪制一次圖片蕉堰,就讓left的值遞增凌净,每次遞增的值為bitmap的寬度,這種方式可以實(shí)現(xiàn)從左往右的繪制圖片出來(lái)填充滿(mǎn)控件屋讶。
具體代碼如下:
protected void onDraw(Canvas canvas) {
//獲取要滾動(dòng)的圖片的寬度
float layerWidth = bitmap.getWidth();
//使用一個(gè)變量來(lái)作為繪制bitmap時(shí)的左邊基坐標(biāo)
float left = 0;
//如果left不大于控件的寬度冰寻,則循環(huán)繪制
while (left < getMeasuredWidth()) {
canvas.drawBitmap(bitmap, left, 0, null);
left += layerWidth;
}
}
3.3.2 要點(diǎn)二
根據(jù)speed屬性,來(lái)設(shè)置圖片往后退的速度皿渗。
- 解決方案:通過(guò)不斷修改canvas.drawBitmap()中的left斩芭,根據(jù)speed,不斷的讓left坐標(biāo)變小乐疆,并調(diào)用重繪方法划乖,從而使得圖片不斷后退。關(guān)于left變小的偏移量挤土,使用全局變量offset進(jìn)行記錄琴庵。
具體代碼如下:
private float offset;
@Override
protected void onDraw(Canvas canvas) {
//獲取要滾動(dòng)的圖片的寬度
float layerWidth = bitmap.getWidth();
//使用一個(gè)變量來(lái)作為繪制bitmap的左邊基坐標(biāo)
float left = offset;
//如果left不大于控件的寬度,則循環(huán)繪制
while (left < getMeasuredWidth()) {
canvas.drawBitmap(bitmap, left, 0, null);
left += layerWidth;
}
//全局變量offser用來(lái)記錄left的偏移量
offset = offset-speed;
postInvalidate();
}
這樣寫(xiě)導(dǎo)致left的偏移量越來(lái)越小仰美,代碼會(huì)在手機(jī)屏幕左方看不見(jiàn)的地方瘋狂繪制迷殿,這顯然不太效率。這里多加一個(gè)判斷咖杂,如果offset超過(guò)一定的界限庆寺,就重置。
protected void onDraw(Canvas canvas) {
//獲取要滾動(dòng)的圖片的寬度
float layerWidth = bitmap.getWidth();
if (offset < -layerWidth) {
offset += (floor(abs(offset) / layerWidth) * layerWidth);
//offset = 0;
}
...
}
3.3.3 要點(diǎn)三
如果速度設(shè)置為負(fù)數(shù)诉字,圖片應(yīng)該不再后退懦尝,而是不斷前進(jìn)知纷。
- 解決方案:并讓左方基坐標(biāo)不斷變大,并改變繪制的方向陵霉,改為從右往左的方向繪制圖片屈扎,就可以讓圖片變成向前滾了。
- 如果需要從右向左撩匕,那么開(kāi)始繪制第一個(gè)圖片時(shí)鹰晨,左方的基坐標(biāo)不再是從0開(kāi)始,而是:控件的寬度-圖片bitmap的寬度止毕。
- 在當(dāng)前的情況下模蜡,為了left變量總體趨勢(shì)不斷變小,同時(shí)能夠不斷的讓左方基坐標(biāo)變大扁凛,所以在循環(huán)當(dāng)中忍疾,左方基坐標(biāo)改為getMeasureWidth()-bitmap.getWidth()-left。
- left變量總體趨勢(shì)不斷變小谨朝,offset也應(yīng)該不斷變小卤妒,而當(dāng)前speed為負(fù),所以在對(duì)offset偏移量的減去speed操作時(shí)字币,對(duì)speed采用絕對(duì)值abs则披。
protected void onDraw(Canvas canvas) {
//獲取要滾動(dòng)的圖片的寬度
float layerWidth = bitmap.getWidth();
...
//如果left不大于控件的寬度,則循環(huán)繪制
while (left < getMeasuredWidth()) {
canvas.drawBitmap(bitmap, getBitmapLeft(layerWidth, left), 0, null);
left += layerWidth;
}
//全局變量offser用來(lái)記錄left的偏移量
if(isStarted){
offset = offset-abs(speed);
postInvalidate();
}
}
/**
* @param layerWidth bitmap圖片的寬度
* @return
*/
private float getBitmapLeft(float layerWidth, float left) {
if (speed < 0) {
return getMeasuredWidth() - layerWidth - left;
} else {
return left;
}
}
3.4 功能完善
為了能夠讓開(kāi)發(fā)者自由控制圖片滾動(dòng)洗出,項(xiàng)目中還加了一個(gè)boolean值用來(lái)控制士复。并提供對(duì)應(yīng)的公有方法。
具體代碼如下:
private boolean isStarted = false;
public ScrollingView(Context context, AttributeSet attrs) {
...
start();
}
/**Start the animation*/
public void start() {
if (!isStarted) {
isStarted = true;
postInvalidate();
}
}
protected void onDraw(Canvas canvas) {
...
//全局變量offser用來(lái)記錄left的偏移量
if(isStarted){
offset = offset-speed;
postInvalidate();
}
}
/**Stop the animation*/
public void stop() {
if (isStarted) {
isStarted = false;
invalidate();
}
}
public boolean isStarted(){
return isStarted;
}
結(jié)束語(yǔ)
有興趣的小伙伴可以參考這個(gè)思路翩活,通過(guò)修改drawBitmap方法中top基坐標(biāo)阱洪,實(shí)現(xiàn)一下Image在豎直方向上的無(wú)限滾動(dòng)。