效果圖展示,圖片有點卡,耐心看會,原程序是很流暢的
實現步驟:
- 聲明變量
- 初始化畫筆、文本大小和座標
- onMeasure()適配wrap_content的寬高
- onDraw()畫出根據座標畫出兩段Text
- 監聽點擊事件
- 在Activity中實現點擊事件
實現原理(座標變換原理):整個過程都是基於座標Y的增加和交換進行處理的,Y值都會一直增加到endY,然後進行交換邏輯
步驟一:聲明變量
由於1號店是兩句話的滾動,所以我們也是使用兩句話來實現的
private Paint mPaint;
private float x, startY, endY, firstY, nextStartY, secondY;
//整個View的寬高是以第一個爲標準的,所以第二句話長度必須小於第一句話
private String[] text = {"今日特賣:毛衣3.3折>>>", "公告:全場半價>>>"};
private float textWidth, textHeight;
//滾動速度
private float speech = 0;
private static final int CHANGE_SPEECH = 0x01;
//是否已經在滾動
private boolean isScroll = false;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
步驟二:初始化畫筆、文本大小和座標
以第一句話爲標準來做控件的寬高標準
//初始化畫筆
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//測量文字的寬高,以第一句話爲標準
Rect rect = new Rect();
mPaint.getTextBounds(text[0], 0, text[0].length(), rect);
textWidth = rect.width();
textHeight = rect.height();
//文字開始的x,y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
x = getX() + getPaddingLeft();
startY = getTop() + textHeight + getPaddingTop() - 5;
//文字結束的x,y座標
endY = startY + textHeight + getPaddingBottom();
//下一個文字滾動開始的y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
nextStartY = getTop() - 5;
//記錄開始的座標
firstY = startY;
secondY = nextStartY;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
步驟三:onMeasure()適配wrap_content的寬高
如果學習過自定義View的話,下面的代碼應該很熟悉,就是適配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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
步驟四:onDraw()畫出根據座標畫出兩段Text(已修復:Text停下來時閃一下的bug)
- 通過Handler來改變速度
- 通過isScroll鎖,來控制Handler只改變一次
- 通過invalidate一直重繪兩句話的文字
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//啓動滾動
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的控件時
if (startY > endY || nextStartY > endY) {
if (startY > endY) {
//第一次滾動過後交換值
startY = secondY;
nextStartY = firstY;
} else if (nextStartY > endY) {
//第二次滾動過後交換值
startY = firstY;
nextStartY = secondY;
}
speech = 0;
isScroll = false;
}
invalidate();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CHANGE_SPEECH:
speech = 1f;
break;
}
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
步驟五:監聽點擊事件(已修復:點擊事件錯亂的問題)
在自定義View重寫dispatchTouchEvent處理點擊事件,這個也是模板代碼:
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:
//點擊事件
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
步驟六:在Activity中實現點擊事件
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();
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
佈局文件
<?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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
整個類的源碼:
/**
* =====作者=====
* 許英俊
* =====時間=====
* 2016/10/11.
*/
public class VerTextView extends View {
private Paint mPaint;
private float x, startY, endY, firstY, nextStartY, secondY;
//整個View的寬高是以第一個爲標準的,所以第二句話長度必須小於第一句話
private String[] text = {"今日特賣:毛衣3.3折>>>", "公告:全場半價>>>"};
private float textWidth, textHeight;
//滾動速度
private float speech = 0;
private static final int CHANGE_SPEECH = 0x01;
//是否已經在滾動
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);
//初始化畫筆
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//測量文字的寬高,以第一句話爲標準
Rect rect = new Rect();
mPaint.getTextBounds(text[0], 0, text[0].length(), rect);
textWidth = rect.width();
textHeight = rect.height();
//文字開始的x,y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
x = getX() + getPaddingLeft();
startY = getTop() + textHeight + getPaddingTop() - 5;
//文字結束的x,y座標
endY = startY + textHeight + getPaddingBottom();
//下一個文字滾動開始的y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
nextStartY = getTop() - 5;
//記錄開始的座標
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);
//啓動滾動
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的控件時
if (startY > endY || nextStartY > endY) {
if (startY > endY) {
//第一次滾動過後交換值
startY = secondY;
nextStartY = firstY;
} else if (nextStartY > endY) {
//第二次滾動過後交換值
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:
//點擊事件
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;
}
}