Android自定義View實(shí)現(xiàn)仿1號(hào)店垂直滾動(dòng)廣告條代碼
效果圖展示,圖片有點(diǎn)卡,耐心看會(huì),原程序是很流暢的

實(shí)現(xiàn)步驟:
- 聲明變量
- 初始化畫(huà)筆、文本大小和坐標(biāo)
- onMeasure()適配wrap_content的寬高
- onDraw()畫(huà)出根據(jù)坐標(biāo)畫(huà)出兩段Text
- 監(jiān)聽(tīng)點(diǎn)擊事件
- 在Activity中實(shí)現(xiàn)點(diǎn)擊事件
實(shí)現(xiàn)原理(坐標(biāo)變換原理):整個(gè)過(guò)程都是基于坐標(biāo)Y的增加和交換進(jìn)行處理的,Y值都會(huì)一直增加到endY,然后進(jìn)行交換邏輯

步驟一:聲明變量
由于1號(hào)店是兩句話(huà)的滾動(dòng),所以我們也是使用兩句話(huà)來(lái)實(shí)現(xiàn)的
private Paint mPaint;
private float x, startY, endY, firstY, nextStartY, secondY;
//整個(gè)View的寬高是以第一個(gè)為標(biāo)準(zhǔn)的,所以第二句話(huà)長(zhǎng)度必須小于第一句話(huà)
private String[] text = {"今日特賣(mài):毛衣3.3折>>>", "公告:全場(chǎng)半價(jià)>>>"};
private float textWidth, textHeight;
//滾動(dòng)速度
private float speech = 0;
private static final int CHANGE_SPEECH = 0x01;
//是否已經(jīng)在滾動(dòng)
private boolean isScroll = false;
步驟二:初始化畫(huà)筆、文本大小和坐標(biāo)
以第一句話(huà)為標(biāo)準(zhǔn)來(lái)做控件的寬高標(biāo)準(zhǔn)
//初始化畫(huà)筆 mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setTextSize(30); //測(cè)量文字的寬高,以第一句話(huà)為標(biāo)準(zhǔn) Rect rect = new Rect(); mPaint.getTextBounds(text[0], 0, text[0].length(), rect); textWidth = rect.width(); textHeight = rect.height(); //文字開(kāi)始的x,y坐標(biāo) //由于文字是以基準(zhǔn)線(xiàn)為基線(xiàn)的,文字底部會(huì)突出一點(diǎn),所以向上收5px x = getX() + getPaddingLeft(); startY = getTop() + textHeight + getPaddingTop() - 5; //文字結(jié)束的x,y坐標(biāo) endY = startY + textHeight + getPaddingBottom(); //下一個(gè)文字滾動(dòng)開(kāi)始的y坐標(biāo) //由于文字是以基準(zhǔn)線(xiàn)為基線(xiàn)的,文字底部會(huì)突出一點(diǎn),所以向上收5px nextStartY = getTop() - 5; //記錄開(kāi)始的坐標(biāo) firstY = startY; secondY = nextStartY;
步驟三:onMeasure()適配wrap_content的寬高
如果學(xué)習(xí)過(guò)自定義View的話(huà),下面的代碼應(yīng)該很熟悉,就是適配warp_content的模板代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int size = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
步驟四:onDraw()畫(huà)出根據(jù)坐標(biāo)畫(huà)出兩段Text(已修復(fù):Text停下來(lái)時(shí)閃一下的bug)
通過(guò)Handler來(lái)改變速度
通過(guò)isScroll鎖,來(lái)控制Handler只改變一次
通過(guò)invalidate一直重繪兩句話(huà)的文字
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//啟動(dòng)滾動(dòng)
if (!isScroll) {
mHandler.sendEmptyMessageDelayed(CHANGE_SPEECH, 2000);
isScroll = true;
}
canvas.drawText(text[0], x, startY, mPaint);
canvas.drawText(text[1], x, nextStartY, mPaint);
startY += speech;
nextStartY += speech;
//超出View的控件時(shí)
if (startY > endY || nextStartY > endY) {
if (startY > endY) {
//第一次滾動(dòng)過(guò)后交換值
startY = secondY;
nextStartY = firstY;
} else if (nextStartY > endY) {
//第二次滾動(dòng)過(guò)后交換值
startY = firstY;
nextStartY = secondY;
}
speech = 0;
isScroll = false;
}
invalidate();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CHANGE_SPEECH:
speech = 1f;
break;
}
}
};
步驟五:監(jiān)聽(tīng)點(diǎn)擊事件(已修復(fù):點(diǎn)擊事件錯(cuò)亂的問(wèn)題)
在自定義View重寫(xiě)dispatchTouchEvent處理點(diǎn)擊事件,這個(gè)也是模板代碼:
public onTouchListener listener;
public interface onTouchListener {
void touchListener(String s);
}
public void setListener(onTouchListener listener) {
this.listener = listener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//點(diǎn)擊事件
if (listener != null) {
if (startY >= firstY && nextStartY < firstY) {
listener.touchListener(text[0]);
} else if (nextStartY >= firstY && startY < firstY) {
listener.touchListener(text[1]);
}
}
break;
}
return true;
}
步驟六:在Activity中實(shí)現(xiàn)點(diǎn)擊事件
public class VerTextViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ver_text_view);
VerTextView tv_ver = (VerTextView) findViewById(R.id.tv_ver);
tv_ver.setListener(new VerTextView.onTouchListener() {
@Override
public void touchListener(String s) {
Toast.makeText(VerTextViewActivity.this, s, Toast.LENGTH_LONG).show();
}
});
}
}
布局文件
<?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="match_parent" android:orientation="horizontal"> <ImageView android:layout_width="120dp" android:layout_height="30dp" android:background="@drawable/vertextview" /> <com.handsome.app3.Custom.VerTextView.VerTextView android:id="@+id/tv_ver" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ffffff" android:padding="8dp" /> </LinearLayout>
整個(gè)類(lèi)的源碼:
/**
* =====作者=====
* 許英俊
* =====時(shí)間=====
* 2016/10/11.
*/
public class VerTextView extends View {
private Paint mPaint;
private float x, startY, endY, firstY, nextStartY, secondY;
//整個(gè)View的寬高是以第一個(gè)為標(biāo)準(zhǔn)的,所以第二句話(huà)長(zhǎng)度必須小于第一句話(huà)
private String[] text = {"今日特賣(mài):毛衣3.3折>>>", "公告:全場(chǎng)半價(jià)>>>"};
private float textWidth, textHeight;
//滾動(dòng)速度
private float speech = 0;
private static final int CHANGE_SPEECH = 0x01;
//是否已經(jīng)在滾動(dòng)
private boolean isScroll = false;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CHANGE_SPEECH:
speech = 1f;
break;
}
}
};
public VerTextView(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化畫(huà)筆
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//測(cè)量文字的寬高,以第一句話(huà)為標(biāo)準(zhǔn)
Rect rect = new Rect();
mPaint.getTextBounds(text[0], 0, text[0].length(), rect);
textWidth = rect.width();
textHeight = rect.height();
//文字開(kāi)始的x,y坐標(biāo)
//由于文字是以基準(zhǔn)線(xiàn)為基線(xiàn)的,文字底部會(huì)突出一點(diǎn),所以向上收5px
x = getX() + getPaddingLeft();
startY = getTop() + textHeight + getPaddingTop() - 5;
//文字結(jié)束的x,y坐標(biāo)
endY = startY + textHeight + getPaddingBottom();
//下一個(gè)文字滾動(dòng)開(kāi)始的y坐標(biāo)
//由于文字是以基準(zhǔn)線(xiàn)為基線(xiàn)的,文字底部會(huì)突出一點(diǎn),所以向上收5px
nextStartY = getTop() - 5;
//記錄開(kāi)始的坐標(biāo)
firstY = startY;
secondY = nextStartY;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int size = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//啟動(dòng)滾動(dòng)
if (!isScroll) {
mHandler.sendEmptyMessageDelayed(CHANGE_SPEECH, 2000);
isScroll = true;
}
canvas.drawText(text[0], x, startY, mPaint);
canvas.drawText(text[1], x, nextStartY, mPaint);
startY += speech;
nextStartY += speech;
//超出View的控件時(shí)
if (startY > endY || nextStartY > endY) {
if (startY > endY) {
//第一次滾動(dòng)過(guò)后交換值
startY = secondY;
nextStartY = firstY;
} else if (nextStartY > endY) {
//第二次滾動(dòng)過(guò)后交換值
startY = firstY;
nextStartY = secondY;
}
speech = 0;
isScroll = false;
}
invalidate();
}
public onTouchListener listener;
public interface onTouchListener {
void touchListener(String s);
}
public void setListener(onTouchListener listener) {
this.listener = listener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//點(diǎn)擊事件
if (listener != null) {
if (startY >= firstY && nextStartY < firstY) {
listener.touchListener(text[0]);
} else if (nextStartY >= firstY && startY < firstY) {
listener.touchListener(text[1]);
}
}
break;
}
return true;
}
}
以上所述是小編給大家介紹的Android自定義View實(shí)現(xiàn)仿1號(hào)店垂直滾動(dòng)廣告條代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android中監(jiān)聽(tīng)系統(tǒng)網(wǎng)絡(luò)連接打開(kāi)或者關(guān)閉的實(shí)現(xiàn)代碼
本篇文章對(duì)Android中監(jiān)聽(tīng)系統(tǒng)網(wǎng)絡(luò)連接打開(kāi)或者關(guān)閉的實(shí)現(xiàn)用實(shí)例進(jìn)行了介紹。需要的朋友參考下2013-05-05
Android使用AsyncQueryHandler實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能
這篇文章主要為大家詳細(xì)介紹了Android使用AsyncQueryHandler實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android開(kāi)發(fā)實(shí)現(xiàn)Switch控件修改樣式功能示例【附源碼下載】
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)Switch控件修改樣式功能,涉及Android Switch開(kāi)關(guān)控件樣式設(shè)置與事件響應(yīng)相關(guān)操作技巧,需要的朋友可以參考下2019-04-04
Android實(shí)現(xiàn)帶圓環(huán)的圓形頭像
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)帶圓環(huán)的圓形頭像,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08

