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用Scroller實(shí)現(xiàn)一個(gè)可向上滑動(dòng)的底部導(dǎo)航欄
本篇文章主要介紹了Android用Scroller實(shí)現(xiàn)一個(gè)可上滑的底部導(dǎo)航欄,具有一定的參考價(jià)值,有興趣的小伙伴們可以參考一下2017-07-07
基于Android實(shí)現(xiàn)隨手指移動(dòng)的ImageView
這篇文章主要介紹了基于Android實(shí)現(xiàn)隨手指移動(dòng)的ImageView的相關(guān)資料,需要的朋友可以參考下2016-01-01
Android實(shí)現(xiàn)RecyclerView下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)RecyclerView下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
flutter仿微信底部圖標(biāo)漸變功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了flutter仿微信底部圖標(biāo)漸變功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android開(kāi)屏頁(yè)倒計(jì)時(shí)功能實(shí)現(xiàn)的詳細(xì)教程
本篇文章主要介紹了Android實(shí)現(xiàn)開(kāi)屏頁(yè)倒計(jì)時(shí)功能實(shí)現(xiàn)的詳細(xì)教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android中應(yīng)用多進(jìn)程的整理總結(jié)
Android平臺(tái)支持多進(jìn)程通信,也支持應(yīng)用內(nèi)實(shí)現(xiàn)多進(jìn)程,下面這篇文章主要給大家介紹了關(guān)于Android中應(yīng)用多進(jìn)程的相關(guān)資料,文中介紹的很詳細(xì),相信對(duì)大家具有一定的參考借鑒價(jià)值,有需要的朋友們下面來(lái)一起看看吧。2017-01-01
Android中判斷網(wǎng)絡(luò)連接是否可用及監(jiān)控網(wǎng)絡(luò)狀態(tài)
獲取網(wǎng)絡(luò)信息需要在AndroidManifest.xml文件中加入相應(yīng)的權(quán)限,接下來(lái)詳細(xì)介紹Android中判斷網(wǎng)絡(luò)連接是否可用及監(jiān)控網(wǎng)絡(luò)狀態(tài),感興趣的朋友可以參考下2012-12-12

