Android自定義控件實(shí)現(xiàn)可左右滑動(dòng)的導(dǎo)航條
本文實(shí)例為大家分享了Android實(shí)現(xiàn)可左右滑動(dòng)導(dǎo)航條的具體代碼,供大家參考,具體內(nèi)容如下
先上效果圖:
這個(gè)控件其實(shí)算是比較輕量級(jí)的,相信不少小伙伴都能做出來(lái)。因?yàn)轫?xiàng)目中遇到了一些特殊的定制要求,所以就自己寫(xiě)了一個(gè),這里放出來(lái)。
首先來(lái)分析下這個(gè)控件的功能:
•能夠響應(yīng)左右滑動(dòng),并且能響應(yīng)快速滑動(dòng)
•選擇項(xiàng)和未選擇項(xiàng)有不同的樣式表現(xiàn),比如前景色,背景色,字體大小變粗之內(nèi)的
•在切換選項(xiàng)的時(shí)候,如果當(dāng)前選項(xiàng)未完全呈現(xiàn)在界面前,則自動(dòng)滾動(dòng)直至當(dāng)前選項(xiàng)完全暴露顯示
前兩條還有,簡(jiǎn)簡(jiǎn)單單就實(shí)現(xiàn)了,主要是第三點(diǎn),這才是我自定義這個(gè)控件的原因!那么如果要實(shí)現(xiàn)這個(gè)控件,需要用到哪些知識(shí)呢?
•用Scroller來(lái)實(shí)現(xiàn)控件的滾動(dòng)
•用VelocityTracker來(lái)實(shí)現(xiàn)控件的快速滾動(dòng)
如果上面兩種技術(shù)你都已經(jīng)會(huì)了,那么我們就可以開(kāi)始講解代碼了。首先是一些屬性的Getter/Setter方法,這里采用的鏈?zhǔn)皆O(shè)置法:
public IndicatorView color(int colorDefault, int colorSelected, int colorBg){ this.colorDefault = colorDefault; this.colorSelected = colorSelected; this.colorBg = colorBg; return this; } public IndicatorView textSize(int textSize){ this.textSize = textSize; return this; } public IndicatorView text(String[] texts){ this.texts = texts; return this; } public IndicatorView padding(int[] padding){ this.padding = padding; return this; } public IndicatorView defaultSelect(int defaultSelect){ this.selectItem = defaultSelect; return this; } public IndicatorView lineHeight(int lineHeight){ this.lineHeight = lineHeight; return this; } public IndicatorView listener(OnIndicatorChangedListener listener){ this.listener = listener; return this; } public IndicatorView type(Type type){ this.type = type; return this; }
這里我們將每一個(gè)選項(xiàng)抽象成了一個(gè)Item類(lèi):
public class Item { String text; int colorDefault; int colorSelected; int textSize; boolean isSelected = false; int width; Point drawPoint; int[] padding = new int[4]; Rect rect = new Rect(); }
然后是控件的初始化操作,主要根據(jù)當(dāng)前控件的寬高,以及設(shè)置的一些屬性,進(jìn)行Item選項(xiàng)的初始化:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); //初始化Item initItems(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void initItems(){ items.clear(); measureWidth = 0; for(int i = 0; i < texts.length; i++){ Item item = new Item(); item.text = texts[i]; item.colorDefault = colorDefault; item.colorSelected = colorSelected; item.textSize = textSize; for(int j = 0; j < item.padding.length; j++){ item.padding[j] = padding[j]; } mPaint.setTextSize(item.textSize); item.width = (int)mPaint.measureText(item.text); int dx = 0; if(i - 1 < 0){ dx = 0; }else{ for(int j = 0; j < i; j++){ dx += items.get(j).padding[0] + items.get(j).width + items.get(j).padding[2]; } } int startX = item.padding[0] + dx; Paint.FontMetrics metrics = mPaint.getFontMetrics(); int startY = (int)(height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom); item.drawPoint = new Point(startX, startY); //設(shè)置區(qū)域 item.rect.left = item.drawPoint.x - item.padding[0]; item.rect.top = 0; item.rect.right = item.drawPoint.x + item.width + item.padding[2]; item.rect.bottom = height; //設(shè)置默認(rèn) if(i == selectItem){ item.isSelected = true; } measureWidth += item.rect.width(); items.add(item); } //重繪 invalidate(); }
接下來(lái)是事件處理,邏輯很簡(jiǎn)單。在DOWN時(shí)間記錄坐標(biāo)值,在MOVE中處理控件的滾動(dòng),在UP中處理滾動(dòng)超屏?xí)r的恢復(fù)操作,以及點(diǎn)擊的操作。
@Override public boolean onTouchEvent(MotionEvent event){ if(mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: mTouchX = (int)event.getX(); mTouchY = (int)event.getY(); mMoveX = mTouchX; return true; case MotionEvent.ACTION_MOVE: if(measureWidth > width){ int dx = (int)event.getX() - mMoveX; if(dx > 0){ // 右滑 if(mScroller.getFinalX() > 0){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0); }else{ mScroller.setFinalX(0); } }else{ //左滑 if(mScroller.getFinalX() + width - dx < measureWidth){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0); }else{ mScroller.setFinalX(measureWidth - width); } } mMoveX = (int)event.getX(); invalidate(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(measureWidth > width){ mVelocityTracker.computeCurrentVelocity(1000); int max = Math.max(Math.abs(mScroller.getCurrX()), Math.abs(measureWidth - width - mScroller.getCurrX())); mScroller.fling(mScroller.getFinalX(), mScroller.getFinalY(), (int)-mVelocityTracker.getXVelocity(), (int)-mVelocityTracker.getYVelocity(), 0, max, mScroller.getFinalY(), mScroller.getFinalY()); //手指抬起時(shí),根據(jù)滾動(dòng)偏移量初始化位置 if(mScroller.getCurrX() < 0){ mScroller.abortAnimation(); mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), -mScroller.getCurrX(), 0); }else if(mScroller.getCurrX() + width > measureWidth){ mScroller.abortAnimation(); mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), measureWidth - width - mScroller.getCurrX(), 0); } } if(event.getAction() == MotionEvent.ACTION_UP){ int mUpX = (int)event.getX(); int mUpY = (int)event.getY(); //模擬點(diǎn)擊操作 if(Math.abs(mUpX - mTouchX) <= mTouchSlop && Math.abs(mUpY - mTouchY) <= mTouchSlop){ for(int i = 0; i < items.size(); i++){ if(items.get(i).rect.contains(mScroller.getCurrX() + mUpX, getScrollY() + mUpY)){ setSelected(i); return super.onTouchEvent(event); } } } } break; default: break; } return super.onTouchEvent(event); }
接下來(lái)就是很重要的一段代碼,因?yàn)檫@段代碼,才可以讓未完全顯示的Item選項(xiàng)被選中時(shí)自動(dòng)滾動(dòng)至完全顯示:
public void setSelected(int position){ if(position >= items.size()){ return; } for(int i = 0; i < items.size(); i++){ if(i == position){ items.get(i).isSelected = true; if(i != selectItem){ selectItem = i; //判斷是否需要滑動(dòng)到完全可見(jiàn) if(mScroller.getCurrX() + width < items.get(i).rect.right){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.right - mScroller.getCurrX() - width, mScroller.getFinalY()); } if(items.get(i).rect.left < mScroller.getCurrX()){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.left - mScroller.getCurrX(), mScroller.getFinalY()); } if(listener != null){ listener.onChanged(selectItem); } } }else{ items.get(i).isSelected = false; } } invalidate(); }
然后就是繪制方法了,相當(dāng)于完全代理給了Item來(lái)實(shí)現(xiàn):
@Override protected void onDraw(Canvas canvas){ mPaint.setAntiAlias(true); canvas.drawColor(colorBg); for(Item item : items){ mPaint.setTextSize(item.textSize); if(item.isSelected){ if(type == Type.SelectByLine){ //繪制紅線(xiàn) mPaint.setColor(item.colorSelected); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(new RectF(item.rect.left, item.rect.bottom - lineHeight, item.rect.right, item.rect.bottom), 3, 3, mPaint); }else if(type == Type.SelectByFill){ //繪制紅色背景 mPaint.setColor(getContext().getResources().getColor(android.R.color.holo_red_light)); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(new RectF(item.rect.left + 6, item.rect.top, item.rect.right - 6, item.rect.bottom), item.rect.height() * 5 / 12, item.rect.height() * 5 / 12, mPaint); } mPaint.setColor(item.colorSelected); }else{ mPaint.setColor(item.colorDefault); } canvas.drawText(item.text, item.drawPoint.x, item.drawPoint.y, mPaint); } }
接下來(lái)就是怎么使用這個(gè)控件了,布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"> <cc.wxf.androiddemo.indicator.IndicatorView android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="38dp" /> </RelativeLayout>
MainActvity中:
package cc.wxf.androiddemo; import android.content.Context; import android.content.res.Resources; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import cc.wxf.androiddemo.indicator.IndicatorView; public class MainActivity extends FragmentActivity { private IndicatorView indicatorView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initIndicator(); } private void initIndicator(){ indicatorView = (IndicatorView)findViewById(R.id.indicator); Resources resources = getResources(); indicatorView.color(resources.getColor(android.R.color.black), resources.getColor(android.R.color.holo_red_light), resources.getColor(android.R.color.darker_gray)) .textSize(sp2px(this, 16)) .padding(new int[]{dip2px(this, 14), dip2px(this, 14), dip2px(this, 14), dip2px(this, 14)}) .text(new String[]{"電視劇","電影","綜藝","片花","動(dòng)漫","娛樂(lè)","會(huì)員1","會(huì)員2","會(huì)員3","會(huì)員4","會(huì)員5","會(huì)員6"}) .defaultSelect(0).lineHeight(dip2px(this, 3)) .listener(new IndicatorView.OnIndicatorChangedListener(){ @Override public void onChanged(int position){ } }).commit(); } public static int dip2px(Context context, float dipValue){ final float scale = context.getResources().getDisplayMetrics().density; return (int)(dipValue * scale + 0.5f); } public static int sp2px(Context context, float spValue){ final float scale = context.getResources().getDisplayMetrics().scaledDensity; return (int)(spValue * scale + 0.5f); } @Override protected void onDestroy() { super.onDestroy(); indicatorView.release(); } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android控件之SlidingDrawer(滑動(dòng)式抽屜)詳解與實(shí)例分享
- Android開(kāi)源堆疊滑動(dòng)控件仿探探效果
- Android自定義控件ScrollView實(shí)現(xiàn)上下滑動(dòng)功能
- Android實(shí)現(xiàn)可滑動(dòng)的自定義日歷控件
- Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果
- Android自定義View實(shí)現(xiàn)隨手勢(shì)滑動(dòng)控件
- Android仿微信列表滑動(dòng)刪除之可滑動(dòng)控件(一)
- Android自定義滑動(dòng)解鎖控件使用詳解
- Android自定義控件實(shí)現(xiàn)滑動(dòng)開(kāi)關(guān)效果
- Android自定義雙向滑動(dòng)控件
相關(guān)文章
Android中TextView動(dòng)態(tài)設(shè)置縮進(jìn)距離的方法
項(xiàng)目需求如果在項(xiàng)目中第一行文字需要添加布局的情況我們應(yīng)該怎么做呢,經(jīng)過(guò)一番考慮和查找我最終選擇了縮進(jìn)的方式解決這個(gè)問(wèn)題,這篇文章主要給大家介紹了關(guān)于Android中TextView動(dòng)態(tài)設(shè)置縮進(jìn)距離的相關(guān)資料,需要的朋友可以參考下2022-04-04Android 個(gè)人理財(cái)工具四:添加賬單頁(yè)面 下
本文主要介紹Android 個(gè)人理財(cái)工具添加賬單頁(yè)面,這里是添加賬單的詳情頁(yè)面及如何使用Android Spinner控件的簡(jiǎn)單示例,有需要的小伙伴可以參考下2016-08-08Android 超詳細(xì)深刨Activity Result API的使用
這篇文章主要介紹了Android開(kāi)發(fā)中Activity Result API的使用,掌握了它以后你就可以放棄startActivityForResult了,感興趣的朋友一起來(lái)看看吧2022-03-03一文帶你搞清楚Android游戲發(fā)行切包資源ID那點(diǎn)事
這篇文章主要介紹了Android 解決游戲發(fā)行切包資源ID的一些問(wèn)題,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2023-05-05Android編程四大組件之Activity用法實(shí)例分析
這篇文章主要介紹了Android編程四大組件之Activity用法,實(shí)例分析了Activity的創(chuàng)建,生命周期,內(nèi)存管理及啟動(dòng)模式等,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01Android 實(shí)現(xiàn)帶進(jìn)度條的WebView的實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)帶進(jìn)度條的WebView的實(shí)例的相關(guān)資料,這里介紹了Webview加載網(wǎng)頁(yè)的方法及帶進(jìn)度的Drawable文件view_progress_webview的實(shí)現(xiàn),需要的朋友可以參考下2017-07-07Android用RecyclerView實(shí)現(xiàn)動(dòng)態(tài)添加本地圖片
本篇文章主要介紹了Android用RecyclerView實(shí)現(xiàn)動(dòng)態(tài)添加本地圖片,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Android關(guān)于Button背景或樣式失效問(wèn)題解決方法
大家好,本篇文章主要講的是Android關(guān)于Button背景或樣式失效問(wèn)題解決方法,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下2022-01-01Flutter中如何加載并預(yù)覽本地的html文件的方法
這篇文章主要介紹了Flutter中如何加載并預(yù)覽本地的html文件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11