Android仿京東首頁輪播文字效果
京東客戶端的輪播文字效果:

本次要實(shí)現(xiàn)的只是后面滾動(dòng)的文字(前面的用ImageView或者TextView實(shí)現(xiàn)即可),看一下實(shí)現(xiàn)的效果

實(shí)現(xiàn)思路

上圖只是一個(gè)大概的思路,要實(shí)現(xiàn)還需要完善更多的細(xì)節(jié),下面會(huì)一步步的來實(shí)現(xiàn)這個(gè)效果:
1.封裝數(shù)據(jù)源:從圖上可以看到,輪播的文字是分為兩個(gè)部分的,暫且把它們分別叫做前綴和內(nèi)容,而且實(shí)際的使用過程中點(diǎn)擊輪播圖肯定是需要跳轉(zhuǎn)頁面的,而且大部分應(yīng)該是WebView,不妨我們就設(shè)置點(diǎn)擊時(shí)候需要獲取的內(nèi)容就是一個(gè)鏈接,那么數(shù)據(jù)源的結(jié)構(gòu)就很明了了

創(chuàng)建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;
}
}
2.接下來應(yīng)該是定制這個(gè)自定義View了,首先理一下思路,看一個(gè)構(gòu)造圖

實(shí)現(xiàn)這個(gè)自定義View的所有參數(shù)都在上表列出了,大部分參數(shù)很容易理解,個(gè)別參數(shù)加進(jìn)去是很有必要的,比如說是否初始化進(jìn)入文字的縱坐標(biāo),文字是否在移動(dòng)中這兩個(gè)參數(shù),之后的內(nèi)容會(huì)詳細(xì)的敘述一下. 在動(dòng)手繪制之前還得需要知道一點(diǎn)基礎(chǔ)的知識(shí),就是關(guān)于繪制文字的方法,里面有很多細(xì)節(jié)需要處理

方法都比較好理解,繪制指定字符串(可以指定范圍)在坐標(biāo)( x , y )處,但是其中的x,y并不是我們所理解的應(yīng)該是文字左上角的坐標(biāo)點(diǎn).其中的x坐標(biāo)是根據(jù)Paint的屬性可變換的,默認(rèn)的x是文字的左邊坐標(biāo),如果Paint設(shè)置了paint.setTextAlign(Paint.Align.CENTER);那就是字符的中心位置.Y坐標(biāo)是文字的baseline的y坐標(biāo). 關(guān)于繪制文字的baseline:
用圖來說話吧

圖中藍(lán)色的線即為baseline,可以看出他既不是頂部坐標(biāo)也不是底部坐標(biāo),那么當(dāng)我們繪制文字的時(shí)候肯定是希望能把文字繪制在正中間.這時(shí)候就要引入paint.getTextBound()方法了 getTextBounds(String text, int start, int end, Rect bounds),傳入一個(gè)Rect對(duì)象,調(diào)用此方法之后則會(huì)填充這個(gè)rect對(duì)象,而填充的內(nèi)容就是所繪制的文字相對(duì)于baseline的偏移坐標(biāo),將這個(gè)Rect加上baseline的坐標(biāo),繪制后是這樣的:

但其實(shí)他的值只是(2,-25,76,3),是相對(duì)于baseline的位置,畫個(gè)圖會(huì)比較好理解

那么要將文字繪制在中間那么實(shí)際繪制baseline的坐標(biāo)應(yīng)該是組件的中心加上文字中心即圖中框的中間坐標(biāo)相對(duì)于baseline的偏移值">那么要將文字繪制在中間,那么實(shí)際繪制baseline的坐標(biāo)應(yīng)該是組件的中心,加上文字中心(即圖中框的中間坐標(biāo))相對(duì)于baseline的偏移值

這張圖中應(yīng)該會(huì)好理解實(shí)際繪制文字的坐標(biāo)與組件中心坐標(biāo)的關(guān)系.關(guān)于偏移值的計(jì)算,按常規(guī)的幾何計(jì)算方法,應(yīng)該是組件的中心坐標(biāo)+偏移值的絕對(duì)值==baseline坐標(biāo)(即實(shí)際繪制的坐標(biāo)),但是由于框的坐標(biāo)值都是相對(duì)于baseline來計(jì)算的,top為負(fù)值,botton為正值,那么這個(gè)偏移值就可以直接用(top+bottom)/2來表示,沒看懂的同學(xué)可以畫個(gè)草圖,用top=-25,bottom=3來算一下,看是否結(jié)果是一致的.
經(jīng)過上面的理解,那我們來繪制正確繪制文字的方法也就確定了
已獲得組件的高度int mHeight , 文字外框Rect bound的情況下
繪制文字在正中間
mHeight / 2 - (bound.top + bound.bottom) / 2 //在縱坐標(biāo)為mY的地方繪制文字 //計(jì)算方式 //mheight /2 = mY + (bound.top + bound.bottom) / 2 ;
文字滾動(dòng)到最高點(diǎn)
mY == 0 - bound.bottom //在縱坐標(biāo)為mY的地方繪制,此時(shí)文字剛好移動(dòng)到最高點(diǎn) //計(jì)算方式 //mY + bound.bottom = 0 ;
文字滾動(dòng)到最低點(diǎn),剛好滾出組件
mY = mHeight - indexBound.top; //在縱坐標(biāo)為mY的地方繪制,此時(shí)文字剛好移動(dòng)到最高點(diǎn) //計(jì)算方式 //mY + bound.top = mHeight ;
知道了如何正確的繪制文字和邊界情況的坐標(biāo)判斷,下面就到了繪制文字的步驟了
首先初始化數(shù)據(jù),設(shè)置默認(rèn)值
//初始化默認(rèn)值
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);
}
前面的敘述中我們知道,剛開始進(jìn)入的時(shí)候文字應(yīng)該是位于組件的底部的,但是這個(gè)值是需要獲取組件的高度和當(dāng)前顯示文字的情況下來判斷的,所以應(yīng)該放在onDraw內(nèi)來初始化這個(gè)值,所以需要前面的是否初始化的屬性,判斷當(dāng)mY==0并且未初始化的時(shí)候給mY賦值.
接下來就是onDraw內(nèi)的處理
獲取當(dāng)前的數(shù)據(jù)
//獲取當(dāng)前的數(shù)據(jù) ADEnity model = mTexts.get(mIndex); String font = model.getmFront(); String back = model.getmBack(); // 繪制前綴的外框 Rect indexBound = new Rect(); mPaintFront.getTextBounds(font, 0, font.length(), indexBound); //繪制內(nèi)容的外框 Rect contentBound = new Rect(); mPaintBack.getTextBounds(back, 0, back.length(), contentBound);
對(duì)mY進(jìn)行初始化
if (mY == 0 && hasInit == false) {
mY = getMeasuredHeight() - indexBound.top;
hasInit = true;
}
對(duì)邊界情況的處理
/
/移動(dòng)到最上面
if (mY == 0 - indexBound.bottom) {
Log.i(TAG, "onDraw: " + getMeasuredHeight());
mY = getMeasuredHeight() - indexBound.top;//返回底部
mIndex++;//換下一組數(shù)據(jù)
}
//移動(dòng)到中間
if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
isMove = false;//停止移動(dòng)
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();//通知重繪
isMove = true;//設(shè)置移動(dòng)為true
}
}, mInterval);//停頓多少毫秒之后再次移動(dòng)
}
移動(dòng)的處理與數(shù)據(jù)源的處理
mY -= 1;//每次只移動(dòng)一個(gè)像素,盡量保證平滑顯示
//循環(huán)使用數(shù)據(jù)
if (mIndex == mTexts.size()) {
mIndex = 0;
}
//如果是處于移動(dòng)狀態(tài)時(shí)的,則延遲繪制
//計(jì)算公式為一個(gè)比例,一個(gè)時(shí)間間隔移動(dòng)組件高度,則多少毫秒來移動(dòng)1像素
if (isMove) {
postInvalidateDelayed(mDuration / getMeasuredHeight());
}
至此對(duì)邏輯的處理就完成了,接下來要設(shè)置點(diǎn)擊事件
public interface onClickLitener {
public void onClick(String mUrl);
}
private onClickLitener onClickLitener;
public void setOnClickLitener(TextViewAd.onClickLitener onClickLitener) {
this.onClickLitener = onClickLitener;
}
//重寫onTouchEvent事件,并且要返回true,表明當(dāng)前的點(diǎn)擊事件由這個(gè)組件自身來處理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//調(diào)用回調(diào),將當(dāng)前數(shù)據(jù)源的鏈接傳出去
if (onClickLitener != null) {
onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
}
break;
}
return true;
}
暴露一些其他屬性的設(shè)置方式
//設(shè)置數(shù)據(jù)源
public void setmTexts(List mTexts) {
this.mTexts = mTexts;
}
//設(shè)置廣告文字的停頓時(shí)間
public void setmInterval(int mInterval) {
this.mInterval = mInterval;
}
//設(shè)置文字從出現(xiàn)到消失的時(shí)長(zhǎng)
public void setmDuration(int mDuration) {
this.mDuration = mDuration;
}
//設(shè)置前綴的文字顏色
public void setFrontColor(int mFrontColor) {
mPaintFront.setColor(mFrontColor);
}
//設(shè)置正文內(nèi)容的顏色
public void setBackColor(int mBackColor) {
mPaintBack.setColor(mBackColor);
}
有興趣的同學(xué)可以將這些屬性設(shè)置到attrs.xml文件中然后就可以在布局文件中設(shè)置屬性了,這里就不演示了,因?yàn)橛X得每次copy這個(gè)View還得把xml文件也copy比較麻煩,畢竟as有自動(dòng)補(bǔ)全,可以很方便的看到暴露在外面的方法.(個(gè)人感受而已).
貼一下完整的ADTextView的代碼,方便查看
package com.qiyuan.jindongshangcheng.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.qiyuan.jindongshangcheng.enity.ADEnity;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by huanghaojie on 2016/9/30.
*/
public class TextViewAd extends TextView {
private int mDuration; //文字從出現(xiàn)到顯示消失的時(shí)間
private int mInterval; //文字停留在中間的時(shí)長(zhǎng)切換的間隔
private List<ADEnity> mTexts; //顯示文字的數(shù)據(jù)源
private int mY = 0; //文字的Y坐標(biāo)
private int mIndex = 0; //當(dāng)前的數(shù)據(jù)下標(biāo)
private Paint mPaintBack; //繪制內(nèi)容的畫筆
private Paint mPaintFront; //繪制前綴的畫筆
private boolean isMove = true; //文字是否移動(dòng)
private String TAG = "ADTextView";
private boolean hasInit = false;//是否初始化剛進(jìn)入時(shí)候文字的縱坐標(biāo)
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();
}
//重寫onTouchEvent事件,并且要返回true,表明當(dāng)前的點(diǎn)擊事件由這個(gè)組件自身來處理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//調(diào)用回調(diào),將當(dāng)前數(shù)據(jù)源的鏈接傳出去
if (onClickLitener != null) {
onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
}
break;
}
return true;
}
//設(shè)置數(shù)據(jù)源
public void setmTexts(List mTexts) {
this.mTexts = mTexts;
}
//設(shè)置廣告文字的停頓時(shí)間
public void setmInterval(int mInterval) {
this.mInterval = mInterval;
}
//設(shè)置文字從出現(xiàn)到消失的時(shí)長(zhǎng)
public void setmDuration(int mDuration) {
this.mDuration = mDuration;
}
//設(shè)置前綴的文字顏色
public void setFrontColor(int mFrontColor) {
mPaintFront.setColor(mFrontColor);
}
//設(shè)置正文內(nèi)容的顏色
public void setBackColor(int mBackColor) {
mPaintBack.setColor(mBackColor);
}
//初始化默認(rèn)值
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);
//獲取當(dāng)前的數(shù)據(jù)
ADEnity model = mTexts.get(mIndex);
String font = model.getmFront();
String back = model.getmBack();
// 繪制前綴的外框
Rect indexBound = new Rect();
mPaintFront.getTextBounds(font, 0, font.length(), indexBound);
//繪制內(nèi)容的外框
Rect contentBound = new Rect();
mPaintBack.getTextBounds(back, 0, back.length(), contentBound);
//剛開始進(jìn)入的時(shí)候文字應(yīng)該是位于組件的底部的 ,但是這個(gè)值是需要獲取組件的高度和當(dāng)前顯示文字的情況下來判斷的,
// 所以應(yīng)該放在onDraw內(nèi)來初始化這個(gè)值,所以需要前面的是否初始化的屬性,判斷當(dāng)mY==0并且未初始化的時(shí)候給mY賦值.
if (mY == 0 && hasInit == false) {
mY = getMeasuredHeight() - indexBound.top;
hasInit = true;
}
//移動(dòng)到最上面
if (mY == 0 - indexBound.bottom) {
Log.i(TAG, "onDraw: " + getMeasuredHeight());
mY = getMeasuredHeight() - indexBound.top;//返回底部
mIndex++;//換下一組數(shù)據(jù)
}
canvas.drawText(back, 0, back.length(), (indexBound.right - indexBound.left) + 20, mY, mPaintBack);
canvas.drawText(font, 0, font.length(), 10, mY, mPaintFront);
//移動(dòng)到中間
if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
isMove = false;//停止移動(dòng)
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();//通知重繪
isMove = true;//設(shè)置移動(dòng)為true
}
}, mInterval);//停頓多少毫秒之后再次移動(dòng)
}
//移動(dòng)的處理與數(shù)據(jù)源的處理
mY -= 1;//每次只移動(dòng)一個(gè)像素,盡量保證平滑顯示
//循環(huán)使用數(shù)據(jù)
if (mIndex == mTexts.size()) {
mIndex = 0;
}
//如果是處于移動(dòng)狀態(tài)時(shí)的,則延遲繪制
//計(jì)算公式為一個(gè)比例,一個(gè)時(shí)間間隔移動(dòng)組件高度,則多少毫秒來移動(dòng)1像素
if (isMove) {
postInvalidateDelayed(mDuration / getMeasuredHeight());
}
}
}
}
怎么使用呢?
1,現(xiàn)在xml文件里引入這個(gè)自定義控件
<?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="vertical">
<com.qiyuan.jindongshangcheng.view.TextViewAd
android:id="@+id/textad"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
2.在MainActivity中使用
/**
* Created by huanghaojie on 2016/9/30.
*/
public class MainActivity extends Activity {
private TextViewAd textViewAd;
private List<ADEnity> mList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);
textViewAd = (TextViewAd) findViewById(R.id.textad);
mList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ADEnity adEnity = new ADEnity("前綴" + i, "后綴" + i, "http://www.baidu.com"+i);
mList.add(adEnity);
}
textViewAd.setmTexts(mList);
textViewAd.setFrontColor(Color.RED);
textViewAd.setBackColor(Color.BLUE);
textViewAd.setmDuration(1000);
textViewAd.setmInterval(1000);
textViewAd.setOnClickLitener(new TextViewAd.onClickLitener() {
@Override
public void onClick(String mUrl) {
Toast.makeText(MainActivity.this,"點(diǎn)擊了"+mUrl,Toast.LENGTH_LONG).show();
}
});
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)圖片文字識(shí)別
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圖片文字識(shí)別,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07
Android實(shí)現(xiàn)熱門標(biāo)簽的流式布局
這篇文章主要介紹了Android實(shí)現(xiàn)熱門標(biāo)簽的流式布局的詳細(xì)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12
詳解Android 多級(jí)聯(lián)動(dòng)控件實(shí)現(xiàn)思路討論
這篇文章主要介紹了詳解Android 多級(jí)聯(lián)動(dòng)控件實(shí)現(xiàn)思路討論,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Android仿IOS UIAlertView對(duì)話框
這篇文章主要為大家詳細(xì)介紹了Android仿IOS UIAlertView對(duì)話框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
android自定義ListView實(shí)現(xiàn)底部View自動(dòng)隱藏和消失的功能
本篇文章主要介紹了android自定義ListView實(shí)現(xiàn)底部View自動(dòng)隱藏和消失的功能 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
Android webview與js的數(shù)據(jù)交互
有了WebView這個(gè)組件,Android應(yīng)用開發(fā)技術(shù)也就轉(zhuǎn)嫁到html與java數(shù)據(jù)交互上來。說白了就是js與WebView的數(shù)據(jù)交互,這就是本文所要討論的2017-04-04
Android實(shí)現(xiàn)引導(dǎo)頁的圓點(diǎn)指示器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)引導(dǎo)頁的圓點(diǎn)指示器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06

