Android仿天天動(dòng)聽(tīng)歌曲自動(dòng)滾動(dòng)view
最近項(xiàng)目中要做一個(gè)類似天天動(dòng)聽(tīng)歌曲自動(dòng)滾動(dòng)行數(shù)的效果。首先自己想了下Android要滾動(dòng)的那就是scroller類或者scrollto、scrollby結(jié)合了,或者view.layout()方法,或者使用動(dòng)畫。但是要循環(huán)滾動(dòng),貌似這些到最后一行滾動(dòng)到第一行都有往回滾的效果,都不是很好的解決方法。怎么會(huì)忘記了可以繪制事件萬(wàn)物的的canvas呢。好吧,既然找到了,那就用這個(gè)方案吧!但是天天動(dòng)聽(tīng)歌曲還有一個(gè)手動(dòng)滑動(dòng)的效果,貌似這篇文章沒(méi)寫。既然這樣,那就自己來(lái)寫下吧!實(shí)現(xiàn)之前還是先看下天天動(dòng)聽(tīng)的效果:

正文
想法1:獲取滑動(dòng)的距離,然后計(jì)算滑動(dòng)了多少行,然后更新數(shù)據(jù)。實(shí)現(xiàn)起來(lái)貌似效果不咋地。
想法2:我們可以看的出來(lái)他滾動(dòng)是一行一行的滾動(dòng)的,只是根據(jù)滾動(dòng)的快慢來(lái)決定滾動(dòng)行數(shù)的快慢。既然這樣的話,只要滾動(dòng)了,就一定時(shí)間的去一行行的滾動(dòng),然后根據(jù)滾動(dòng)的速度來(lái)決定更新的間隔時(shí)間。
嗯,想好了怎么實(shí)現(xiàn),現(xiàn)在就來(lái)寫代碼吧。
先來(lái)寫一個(gè)類,繼承TextView
VerticalScrollTextView.class
public class VerticalScrollTextView extends TextView implements Runnable{
//繪制歌詞畫筆
private Paint mContentPaint;
//繪制基線畫筆
private Paint mLinePaint;
//繪制滑動(dòng)進(jìn)度背景畫筆
private Paint mRectPaint;
//歌詞數(shù)據(jù)
private List<Sentence> mDataList;
//行數(shù)
private int index = 0 ;
//當(dāng)前view的寬
private float mX;
//當(dāng)前view的高
private float mY;
//當(dāng)前view垂直方向中線
private float middleY;
//行與行之間的間距
private final static int DY = 80 ;
//歌詞文字大小
private int mTextSize = 35;
//歌詞中間字體的大小
private int mBigTextSize = 45;
//當(dāng)前是否按下
private boolean isTouch = false ;
//上一次觸摸view的y軸坐標(biāo)
private float mLastY;
//是否正在滑動(dòng)
private boolean isMoving;
//記錄上一次滑動(dòng)的時(shí)間
private long lastMoveTime;
//滑動(dòng)速度追蹤類
private VelocityTracker mVelocityTracker;
//滑動(dòng)最大速度
private int mMaximumVelocity;
//歌詞是否為空
private boolean isEmpty;
public VerticalScrollTextView(Context context) {
this(context,null);
}
public VerticalScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public VerticalScrollTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲取最大的滑動(dòng)速度值
mMaximumVelocity = ViewConfiguration.get(context)
.getScaledMaximumFlingVelocity();
init();
}
private void init(){
setFocusable(true);
setClickable(true);
//歌詞為空設(shè)置默認(rèn)值
if(mDataList==null){
mDataList = new ArrayList<>();
Sentence sentence = new Sentence(0,"沒(méi)有獲取到歌詞",0);
mDataList.add(sentence);
isEmpty = true ;
}
//初始化歌詞畫筆
mContentPaint = new Paint();
mContentPaint.setTextSize(mTextSize);
mContentPaint.setAntiAlias(true);
mContentPaint.setColor(Color.parseColor("#e5e2e2"));
//設(shè)置為serif字體
mContentPaint.setTypeface(Typeface.SERIF);
//設(shè)置字體為居中
mContentPaint.setTextAlign(Paint.Align.CENTER);
//初始化基線畫筆
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(Color.WHITE);
//進(jìn)度背景顏色畫筆
mRectPaint = new Paint();
mLinePaint.setAntiAlias(true);
mRectPaint.setColor(Color.parseColor("#66666666"));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果當(dāng)前進(jìn)度為-1,直接返回,不用繪制
if (index == -1)
return;
Sentence sentence = mDataList.get(index);
//繪制中間行的歌詞,設(shè)置為高亮白色,大字體
mContentPaint.setColor(Color.WHITE);
mContentPaint.setTextSize(mBigTextSize);
canvas.drawText(sentence.getName(), mX/2, middleY, mContentPaint);
//當(dāng)前為歌詞不為空并且按下的情況下,繪制基線和進(jìn)度
if(!isEmpty&&isTouch){
//獲取中間行字體最高的位置
float baseLine = middleY-Math.abs(mContentPaint.ascent());
//繪制進(jìn)度背景
canvas.drawRect(10.0f,baseLine-70,150.0f,baseLine,mRectPaint);
//繪制基線
canvas.drawLine(10.0f,baseLine,mX-10,baseLine,mLinePaint);
//設(shè)置進(jìn)度字體大小
mContentPaint.setTextSize(mTextSize);
//繪制進(jìn)度字體
canvas.drawText(String.valueOf(index),85,baseLine-35,mContentPaint);
}
//初始化isEmpty
isEmpty = false ;
//初始化歌詞內(nèi)容畫筆
mContentPaint.setColor(Color.parseColor("#e5e2e2"));
mContentPaint.setTextSize(mTextSize);
//暫時(shí)保存中間線位置,來(lái)繪制中間線以上的行數(shù)字體
float tempY = middleY;
//繪制中間線以上的歌詞
for (int i = index - 1; i >= 0; i--) {
tempY = tempY - DY;
if (tempY < 0) {
break;
}
Sentence preSentence = mDataList.get(i);
canvas.drawText(preSentence.getName(), mX/2, tempY, mContentPaint);
}
tempY = middleY;
//繪制中間線以下的歌詞
for (int i = index + 1; i < mDataList.size(); i++) {
tempY = tempY + DY;
if (tempY > mY) {
break;
}
Sentence nexeSentence = mDataList.get(i);
canvas.drawText(nexeSentence.getName(), mX/2, tempY, mContentPaint);
}
//初始化isMoving,到這里表示滑動(dòng)結(jié)束
isMoving = false ;
}
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
//獲取view的寬和高
mX = w;
mY = h;
middleY = h * 0.5f;
}
public long updateIndex(int index) {
if (index == -1)
return -1;
this.index=index;
return index;
}
public List<Sentence> getDataList() {
return mDataList;
}
public void setDataList(List<Sentence> mDataList){
this.mDataList = mDataList ;
}
public void updateUI(){
new Thread(this).start();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
isTouch =true;
mLastY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//創(chuàng)建速度追蹤器
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
//獲取當(dāng)前速度。默認(rèn)為100
float velocity = mVelocityTracker.getYVelocity()==0?100:mVelocityTracker.getYVelocity();
long currentTime = System.currentTimeMillis();
//設(shè)置一個(gè)固定值和速度結(jié)合決定滑動(dòng)更新的快慢
if(!isMoving&¤tTime-lastMoveTime>20000/Math.abs(velocity)){
isMoving = true ;
lastMoveTime = System.currentTimeMillis();
float currentY = event.getY();
float mMoveY = currentY - mLastY;
//向上滑動(dòng)-1向下滑動(dòng)+1
int newIndex = mMoveY>0?index - 1:index+1;
//循環(huán)滾動(dòng)
newIndex=newIndex<0?mDataList.size()-1:newIndex>=mDataList.size()?0:newIndex;
updateIndex(newIndex);
invalidate();
mLastY = currentY;
}
break;
case MotionEvent.ACTION_UP:
isTouch = false ;
recycleVelocityTracker();
break;
}
return super.onTouchEvent(event);
}
@Override
public void run() {
//自動(dòng)滾動(dòng)刷新的時(shí)間間隔
long time = 1000;
//控制進(jìn)度
int i=0;
while (true) {
//當(dāng)前不在按下的情況下自動(dòng)滾動(dòng)
if(!isTouch){
//設(shè)置當(dāng)前的進(jìn)度值
long sleeptime = updateIndex(i);
//使用handle刷新ui
mHandler.post(mUpdateResults);
if (sleeptime == -1)
return;
try {
Thread.sleep(time);
i++;
//當(dāng)?shù)搅俗詈笠恍械臅r(shí)候自動(dòng)跳轉(zhuǎn)到第一行
if(i==getDataList().size())
i=0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Handler mHandler = new Handler();
Runnable mUpdateResults = new Runnable() {
public void run() {
invalidate();
}
};
//創(chuàng)建速度追蹤器
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
//釋放
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
自定義view基本就是這樣了,我們可以把要定義的一些屬性寫在attrs里面了,這里就懶得寫了。大概的思路就是先繪制指定的index行的歌詞,然后繪制index上面行的歌詞,然后繪制index下面行的歌詞。然后新建一個(gè)線程,讓它通過(guò)handle隔一定的時(shí)間定時(shí)刷新歌詞行數(shù)。然后在onTouchEvent處理觸摸滾動(dòng)行數(shù),獲取到當(dāng)前滾動(dòng)速度來(lái)決定一個(gè)更新的時(shí)間間隔。從而實(shí)現(xiàn)觸摸滾動(dòng)刷新的快慢?;旧暇褪沁@樣了。其他的看注釋。
再看下初始化數(shù)據(jù)測(cè)試的Activity:
VerticalScrollTextActivity.class
public class VerticalScrollTextActivity extends Activity {
VerticalScrollTextView mSampleView;
String[] str = {"你在南方的艷陽(yáng)里 大雪紛飛",
"我在北方的寒夜里 四季如春",
"如果天黑之前來(lái)的及",
"我要忘了你的眼睛",
"窮極一生 做不完一場(chǎng)夢(mèng)",
"他不在和誰(shuí)談?wù)撓喾甑墓聧u",
"因?yàn)樾睦镌缫鸦臒o(wú)人煙",
"他的心里在裝不下一個(gè)家",
"做一個(gè)只對(duì)自己說(shuō)謊的啞巴",
"他說(shuō)你任何為人稱道的美麗",
"不及他第一次遇見(jiàn)你",
"時(shí)光茍延殘喘 無(wú)可奈何",
"如果所有土地連在一起",
"走上一生只為擁抱你",
"喝醉了他的夢(mèng) 晚安",
"你在南方的艷陽(yáng)里 大雪紛飛",
"我在北方的寒夜里 四季如春",
"如果天黑之前來(lái)的及",
"我要忘了你的眼睛",
"窮極一生 做不完一場(chǎng)夢(mèng)",
"他不在和誰(shuí)談?wù)撓喾甑墓聧u",
"因?yàn)樾睦镌缫鸦臒o(wú)人煙",
"他的心里在裝不下一個(gè)家",
"做一個(gè)只對(duì)自己說(shuō)謊的啞巴",
"他說(shuō)你任何為人稱道的美麗",
"不及他第一次遇見(jiàn)你",
"時(shí)光茍延殘喘 無(wú)可奈何",
"如果所有土地連在一起",
"走上一生只為擁抱你",
"喝醉了他的夢(mèng) 晚安"
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSampleView = (VerticalScrollTextView) findViewById(R.id.sampleView1);
List lst=new ArrayList<>();
for(int i=0;i<str.length;i++){
Sentence sen=new Sentence(i,str[i],i+1202034);
lst.add(i, sen);
}
mSampleView.setDataList(lst);
mSampleView.updateUI();
}
}
模擬了一首歌詞數(shù)據(jù),然后setDataList,在調(diào)用updateUI()就行了。
最后看下布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.goach.lib.VerticalScrollTextView android:id="@+id/sampleView1" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/bg" /> </RelativeLayout>
測(cè)試下,我們就可以看到效果了:

源碼下載:Android仿天天動(dòng)聽(tīng)歌曲自動(dòng)滾動(dòng)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)Android軟件編程有所幫助。
- Android自定義View實(shí)現(xiàn)廣告信息上下滾動(dòng)效果
- Android高仿京東垂直循環(huán)滾動(dòng)新聞欄
- Android PickerView滾動(dòng)選擇器的使用方法
- Android仿淘寶商品瀏覽界面圖片滾動(dòng)效果
- Android程序開(kāi)發(fā)ListView+Json+異步網(wǎng)絡(luò)圖片加載+滾動(dòng)翻頁(yè)的例子(圖片能緩存,圖片不錯(cuò)亂)
- Android仿UC瀏覽器左右上下滾動(dòng)功能
- android開(kāi)發(fā)之橫向滾動(dòng)/豎向滾動(dòng)的ListView(固定列頭)
- android實(shí)現(xiàn)上下滾動(dòng)的TextView
- Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁(yè)的Gridview實(shí)例源碼
- Android新聞廣告條滾動(dòng)效果
相關(guān)文章
詳解Android首選項(xiàng)框架的使用實(shí)例
首選項(xiàng)這個(gè)名詞對(duì)于熟悉Android的朋友們一定不會(huì)感到陌生,它經(jīng)常用來(lái)設(shè)置軟件的運(yùn)行參數(shù)。本篇文章主要介紹詳解Android首選項(xiàng)框架的使用實(shí)例,有興趣的可以了解一下。2016-11-11
設(shè)置界面開(kāi)發(fā)Preference Library數(shù)據(jù)重建機(jī)制詳解
這篇文章主要為大家介紹了設(shè)置界面開(kāi)發(fā)利器Preference Library數(shù)據(jù)重建機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Android自定義View實(shí)現(xiàn)微信語(yǔ)音界面
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)微信語(yǔ)音界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Android Flutter自適應(yīng)瀑布流案例詳解
這篇文章主要介紹了Android Flutter自適應(yīng)瀑布流案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Android編程實(shí)現(xiàn)activity dialog透明背景的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)activity dialog透明背景的方法,涉及Activity相關(guān)屬性設(shè)置及配置文件操作技巧,需要的朋友可以參考下2017-07-07
Android實(shí)現(xiàn)畫板、寫字板功能(附源碼下載)
這篇文章主要介紹了Android實(shí)現(xiàn)畫板、寫字板功能的方法,文中給出了簡(jiǎn)單的介紹和示例代碼,想要了解更多的朋友可以下載源碼進(jìn)行學(xué)習(xí),感興趣的朋友們下面來(lái)一起看看吧。2017-01-01
Android EdText編輯框禁止輸入表情符號(hào)(使用正則表達(dá)式)
這篇文章主要介紹了Android EdText編輯框禁止輸入表情符號(hào)使用正則表達(dá)式,需要的朋友可以參考下2017-06-06
Android利用CountDownTimer實(shí)現(xiàn)驗(yàn)證碼倒計(jì)時(shí)效果實(shí)例
這篇文章主要給大家介紹了關(guān)于Android如何利用CountDownTimer實(shí)現(xiàn)驗(yàn)證碼倒計(jì)時(shí)效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
Android 使用 Scroller 實(shí)現(xiàn)平滑滾動(dòng)功能的示例代碼
這篇文章主要介紹了Android 使用 Scroller 實(shí)現(xiàn)平滑滾動(dòng)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07

