ADEnity
类并完善一些基本的方法,代码如下public class ADEnity {
private String mFront ; //前面的文字
private String mBack ; //后面的文字
private String mUrl ;//包含的链接
public ADEnity(String mFront, String mBack,String mUrl) {
this.mFront = mFront;
this.mBack = mBack;
this.mUrl = mUrl;
}
public String getmUrl() {
return mUrl;
}
public void setmUrl(String mUrl) {
this.mUrl = mUrl;
}
public String getmFront() {
return mFront;
}
public void setmFront(String mFront) {
this.mFront = mFront;
}
public String getmBack() {
return mBack;
}
public void setmBack(String mBack) {
this.mBack = mBack;
}
}
返回值 | 方法 | 描述 |
---|---|---|
void | drawText(String text, float x, float y, Paint paint) | Draw the text, with origin at (x,y), using the specified paint. |
void | drawText(CharSequence text, int start, int end, float x, float y, Paint paint) | Draw the specified range of text, specified by start/end, with its origin at (x,y), in the specified Paint. |
void | drawText(char[] text, int index, int count, float x, float y, Paint paint) | Draw the text, with origin at (x,y), using the specified paint. |
void | drawText(String text, int start, int end, float x, float y, Paint paint) | Draw the text, with origin at (x,y), using the specified paint. |
( x , y )
处,但是其中的x,y
并不是我们所理解的应该是文字左上角的坐标点.其中的x坐标是根据Paint
的属性可变换的,默认的x是文字的左边坐标,如果Paint
设置了paint.setTextAlign(Paint.Align.CENTER)
;那就是字符的中心位置.Y
坐标是文字的baseline
的y
坐标.baseline
:baseline
,可以看出他既不是顶部坐标也不是底部坐标,那么当我们绘制文字的时候肯定是希望能把文字绘制在正中间.这时候就要引入paint.getTextBound()
方法了getTextBounds(String text, int start, int end, Rect bounds)
,传入一个Rect
对象,调用此方法之后则会填充这个rect对象,而填充的内容就是所绘制的文字相对于baseline
的偏移坐标,将这个Rect加上baseline
的坐标,绘制后是这样的:(2,-25,76,3)
,是相对于baseline的位置,画个图会比较好理解组件的中心坐标+偏移值的绝对值==baseline坐标(即实际绘制的坐标)
,但是由于框的坐标值都是相对于baseline
来计算的,top
为负值,botton
为正值,那么这个偏移值就可以直接用(top+bottom)/2
来表示,没看懂的同学可以画个草图,用top=-25
,bottom=3
来算一下,看是否结果是一致的.int mHeight
, 文字外框Rect bound
的情况下mHeight / 2 - (bound.top + bound.bottom) / 2
//在纵坐标为mY的地方绘制文字
//计算方式
//mheight /2 = mY + (bound.top + bound.bottom) / 2 ;
mY == 0 - bound.bottom
//在纵坐标为mY的地方绘制,此时文字刚好移动到最高点
//计算方式
//mY + bound.bottom = 0 ;
mY = mHeight - indexBound.top;
//在纵坐标为mY的地方绘制,此时文字刚好移动到最高点
//计算方式
//mY + bound.top = mHeight ;
//初始化默认值
private void init() {
mIndex = 0 ;
mY = 0 ;
mDuration = 500;
mInterval = 1000;
mPaintFront = new Paint();
mPaintFront.setAntiAlias(true);
mPaintFront.setDither(true);
mPaintFront.setTextSize(30);
mPaintBack = new Paint();
mPaintBack.setAntiAlias(true);
mPaintBack.setDither(true);
mPaintBack.setTextSize(30);
}
onDraw
内来初始化这个值,所以需要前面的是否初始化的属性,判断当mY==0
并且未初始化的时候给mY
赋值.onDraw
内的处理 ADEnity model = mTexts.get(mIndex);
String font = model.getmFront();
String back = model.getmBack();
//绘制前缀的外框
Rect indexBound = new Rect();
mPaintFront.getTextBounds(font, 0, font.length(), indexBound);
//绘制内容的外框
Rect contentBound = new Rect();
mPaintBack.getTextBounds(back, 0, back.length(), contentBound);
mY
进行初始化if (mY == 0 && hasInit == false) {
mY = getMeasuredHeight() - indexBound.top;
hasInit = true;
}
//移动到最上面
if (mY == 0 - indexBound.bottom) {
mY = getMeasuredHeight() - indexBound.top; //返回底部
mIndex++; //换下一组数据
}
//移动到中间
if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
isMove = false; //停止移动
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate(); //通知重绘
isMove = true; //设置移动为true
}
}, mInterval); //停顿多少毫秒之后再次移动
}
mY -= 1; //每次只移动一个像素,尽量保证平滑显示
//循环使用数据
if (mIndex == mTexts.size()) {
mIndex = 0;
}
//如果是处于移动状态时的,则延迟绘制
//计算公式为一个比例,一个时间间隔移动组件高度,则多少毫秒来移动1像素
if (isMove) {
postInvalidateDelayed(mDuration / getMeasuredHeight());
}
//设置一个回调并设置setXXX方法
public interface onClickLitener {
public void onClick(String mUrl);
}
private onClickLitener onClickLitener;
public void setOnClickLitener(TextViewAd.onClickLitener onClickLitener) {
this.onClickLitener = onClickLitener;
}
//重写onTouchEvent事件,并且要返回true,表明当前的点击事件由这个组件自身来处理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (onClickLitener != null) {
//调用回调,将当前数据源的链接传出去 onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
}
break;
}
return true;
}
//设置数据源
public void setmTexts(List<ADEnity> mTexts) {
this.mTexts = mTexts;
}
//设置广告文字的停顿时间
public void setmInterval(int mInterval) {
this.mInterval = mInterval;
}
//设置文字从出现到消失的时长
public void setmDuration(int mDuration) {
this.mDuration = mDuration;
}
//设置前缀的文字颜色
public void setFrontColor(int mFrontColor) {
mPaintFront.setColor(mFrontColor);
}
//设置正文内容的颜色
public void setBackColor(int mBackColor) {
mPaintBack.setColor(mBackColor);
}
attrs.xml
文件中然后就可以在布局文件中设置属性了,这里就不演示了,因为觉得每次copy
这个View
还得把xml
文件也copy
比较麻烦,毕竟as有自动补全,可以很方便的看到暴露在外面的方法.(个人感受而已).package com.brioa.diyviewtest.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
import com.brioa.diyviewtest.model.ADEnity;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by Brioal on 2016/5/28.
*/
public class TextViewAd extends TextView {
private int mDuration; //文字从出现到显示消失的时间
private int mInterval; //文字停留在中间的时长切换的间隔
private List<ADEnity> mTexts; //显示文字的数据源
private int mY = 0; //文字的Y坐标
private int mIndex = 0; //当前的数据下标
private Paint mPaintBack; //绘制内容的画笔
private Paint mPaintFront; //绘制前缀的画笔
private boolean isMove = true; //文字是否移动
private String TAG = "ADTextView";
private boolean hasInit = false;
public interface onClickLitener {
public void onClick(String mUrl);
}
private onClickLitener onClickLitener;
public void setOnClickLitener(TextViewAd.onClickLitener onClickLitener) {
this.onClickLitener = onClickLitener;
}
public TextViewAd(Context context) {
this(context, null);
}
public TextViewAd(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (onClickLitener != null) {
onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
}
break;
}
return true;
}
//设置数据源
public void setmTexts(List<ADEnity> mTexts) {
this.mTexts = mTexts;
}
//设置广告文字的停顿时间
public void setmInterval(int mInterval) {
this.mInterval = mInterval;
}
//设置文字从出现到消失的时长
public void setmDuration(int mDuration) {
this.mDuration = mDuration;
}
//设置前缀的文字颜色
public void setFrontColor(int mFrontColor) {
mPaintFront.setColor(mFrontColor);
}
//设置正文内容的颜色
public void setBackColor(int mBackColor) {
mPaintBack.setColor(mBackColor);
}
//初始化默认值
private void init() {
mDuration = 500;
mInterval = 1000;
mIndex = 0;
mPaintFront = new Paint();
mPaintFront.setAntiAlias(true);
mPaintFront.setDither(true);
mPaintFront.setTextSize(30);
mPaintBack = new Paint();
mPaintBack.setAntiAlias(true);
mPaintBack.setDither(true);
mPaintBack.setTextSize(30);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.i(TAG, "onSizeChanged: " + h);
}
@Override
protected void onDraw(Canvas canvas) {
if (mTexts != null) {
Log.i(TAG, "onDraw: " + mY);
ADEnity model = mTexts.get(mIndex);
String font = model.getmFront();
String back = model.getmBack();
//绘制前缀
Rect indexBound = new Rect();
mPaintFront.getTextBounds(font, 0, font.length(), indexBound);
//绘制内容文字
Rect contentBound = new Rect();
mPaintBack.getTextBounds(back, 0, back.length(), contentBound);
if (mY == 0 && hasInit == false) {
mY = getMeasuredHeight() - indexBound.top;
hasInit = true;
}
//移动到最上面
if (mY == 0 - indexBound.bottom) {
Log.i(TAG, "onDraw: " + getMeasuredHeight());
mY = getMeasuredHeight() - indexBound.top;
mIndex++;
}
canvas.drawText(back, 0, back.length(), (indexBound.right - indexBound.left) + 20, mY, mPaintBack);
canvas.drawText(font, 0, font.length(), 10, mY, mPaintFront);
//移动到中间
if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
isMove = false;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();
isMove = true;
}
}, mInterval);
}
mY -= 1;
//循环使用数据
if (mIndex == mTexts.size()) {
mIndex = 0;
}
//如果是处于移动状态时的,则延迟绘制
//计算公式为一个比例,一个时间间隔移动组件高度,则多少毫秒来移动1像素
if (isMove) {
postInvalidateDelayed(mDuration / getMeasuredHeight());
}
}
}
}
github
,见谅.View
就完成了,有不足的地方欢迎指出,另外建了个新手交流Android
开发的QQ
群,欢迎加入.375276053
原文:http://blog.csdn.net/qq_26971803/article/details/51537129