Android自定义控件实现可左右滑动的导航条

 更新时间:2020年04月08日 11:04:39   作者:凤求凰丶  
这篇文章主要介绍了Android自定义控件实现可左右滑动的导航条,能响应快速滑动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!
如果你想靠AI翻身,你先需要一个靠谱的工具!

本文实例为大家分享了Android实现可左右滑动导航条的具体代码,供大家参考,具体内容如下

先上效果图:

这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。

首先来分析下这个控件的功能:

•能够响应左右滑动,并且能响应快速滑动
•选择项和未选择项有不同的样式表现,比如前景色,背景色,字体大小变粗之内的
•在切换选项的时候,如果当前选项未完全呈现在界面前,则自动滚动直至当前选项完全暴露显示

前两条还有,简简单单就实现了,主要是第三点,这才是我自定义这个控件的原因!那么如果要实现这个控件,需要用到哪些知识呢? 
•用Scroller来实现控件的滚动
•用VelocityTracker来实现控件的快速滚动 

如果上面两种技术你都已经会了,那么我们就可以开始讲解代码了。首先是一些属性的Getter/Setter方法,这里采用的链式设置法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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类:

1
2
3
4
5
6
7
8
9
10
11
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();
}

然后是控件的初始化操作,主要根据当前控件的宽高,以及设置的一些属性,进行Item选项的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@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);
 //设置区域
 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;
 //设置默认
 if(i == selectItem){
 item.isSelected = true;
 }
 measureWidth += item.rect.width();
 items.add(item);
}
//重绘
invalidate();
}

 接下来是事件处理,逻辑很简单。在DOWN时间记录坐标值,在MOVE中处理控件的滚动,在UP中处理滚动超屏时的恢复操作,以及点击的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@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());
  //手指抬起时,根据滚动偏移量初始化位置
  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选项被选中时自动滚动至完全显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
}

然后就是绘制方法了,相当于完全代理给了Item来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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);
}
}

接下来就是怎么使用这个控件了,布局文件:

1
2
3
4
5
6
7
8
9
10
11
<?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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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();
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

相关文章

最新评论