Android自定義控件實現(xiàn)可左右滑動的導(dǎo)航條
本文實例為大家分享了Android實現(xiàn)可左右滑動導(dǎo)航條的具體代碼,供大家參考,具體內(nèi)容如下
先上效果圖:

這個控件其實算是比較輕量級的,相信不少小伙伴都能做出來。因為項目中遇到了一些特殊的定制要求,所以就自己寫了一個,這里放出來。
首先來分析下這個控件的功能:
•能夠響應(yīng)左右滑動,并且能響應(yīng)快速滑動
•選擇項和未選擇項有不同的樣式表現(xiàn),比如前景色,背景色,字體大小變粗之內(nèi)的
•在切換選項的時候,如果當(dāng)前選項未完全呈現(xiàn)在界面前,則自動滾動直至當(dāng)前選項完全暴露顯示
前兩條還有,簡簡單單就實現(xiàn)了,主要是第三點,這才是我自定義這個控件的原因!那么如果要實現(xiàn)這個控件,需要用到哪些知識呢?
•用Scroller來實現(xiàn)控件的滾動
•用VelocityTracker來實現(xiàn)控件的快速滾動
如果上面兩種技術(shù)你都已經(jīng)會了,那么我們就可以開始講解代碼了。首先是一些屬性的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;
}
這里我們將每一個選項抽象成了一個Item類:
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選項的初始化:
@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();
}
接下來是事件處理,邏輯很簡單。在DOWN時間記錄坐標(biāo)值,在MOVE中處理控件的滾動,在UP中處理滾動超屏?xí)r的恢復(fù)操作,以及點擊的操作。
@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());
//手指抬起時,根據(jù)滾動偏移量初始化位置
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();
//模擬點擊操作
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);
}
接下來就是很重要的一段代碼,因為這段代碼,才可以讓未完全顯示的Item選項被選中時自動滾動至完全顯示:
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;
//判斷是否需要滑動到完全可見
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來實現(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){
//繪制紅線
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);
}
}
接下來就是怎么使用這個控件了,布局文件:
<?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[]{"電視劇","電影","綜藝","片花","動漫","娛樂","會員1","會員2","會員3","會員4","會員5","會員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();
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中TextView動態(tài)設(shè)置縮進(jìn)距離的方法
項目需求如果在項目中第一行文字需要添加布局的情況我們應(yīng)該怎么做呢,經(jīng)過一番考慮和查找我最終選擇了縮進(jìn)的方式解決這個問題,這篇文章主要給大家介紹了關(guān)于Android中TextView動態(tài)設(shè)置縮進(jìn)距離的相關(guān)資料,需要的朋友可以參考下2022-04-04
Android 超詳細(xì)深刨Activity Result API的使用
這篇文章主要介紹了Android開發(fā)中Activity Result API的使用,掌握了它以后你就可以放棄startActivityForResult了,感興趣的朋友一起來看看吧2022-03-03
一文帶你搞清楚Android游戲發(fā)行切包資源ID那點事
這篇文章主要介紹了Android 解決游戲發(fā)行切包資源ID的一些問題,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2023-05-05
Android 實現(xiàn)帶進(jìn)度條的WebView的實例
這篇文章主要介紹了Android 實現(xiàn)帶進(jìn)度條的WebView的實例的相關(guān)資料,這里介紹了Webview加載網(wǎng)頁的方法及帶進(jìn)度的Drawable文件view_progress_webview的實現(xiàn),需要的朋友可以參考下2017-07-07
Android用RecyclerView實現(xiàn)動態(tài)添加本地圖片
本篇文章主要介紹了Android用RecyclerView實現(xiàn)動態(tài)添加本地圖片,具有一定的參考價值,有興趣的可以了解一下2017-08-08
Android關(guān)于Button背景或樣式失效問題解決方法
大家好,本篇文章主要講的是Android關(guān)于Button背景或樣式失效問題解決方法,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
Flutter中如何加載并預(yù)覽本地的html文件的方法
這篇文章主要介紹了Flutter中如何加載并預(yù)覽本地的html文件的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

