先来个效果: (可以左右滑动,可以选中上一个,下一个,点击具体项选中那项)
(中间字体大,两边字体小,稍微有点拱桥形状,实现在onDraw后面几行代码,可自行优化)
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HSelectView">
<!--可见数目-->
<attr name="ViewSeesize" format="integer"></attr>
<!--被选择文字的大小和颜色-->
<attr name="selectedTextSize" format="float"></attr>
<attr name="selectedTextColor" format="color|reference"></attr>
<!--未被被选择文字的大小和颜色-->
<attr name="textSize" format="float"></attr>
<attr name="textColor" format="color|reference"></attr>
</declare-styleable>
</resources>
初始化(不多赘述)
/**
* 初始化画笔
*/
private void initPaint() {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(textSize);
textPaint.setColor(textColor);
selectedPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
selectedPaint.setColor(selectedColor);
selectedPaint.setTextSize(selectedTextSize);
}
/**
* 初始化属性
*
* @param attrs
*/
private void initAttrs(AttributeSet attrs) {
TypedArray tta = context.obtainStyledAttributes(attrs,
R.styleable.HSelectView);
//两种字体颜色和字体大小
seeSize = tta.getInteger(R.styleable.HSelectView_ViewSeesize, 5);
selectedTextSize = tta.getFloat(R.styleable.HSelectView_selectedTextSize, 50);
selectedColor = tta.getColor(R.styleable.HSelectView_selectedTextColor, context.getResources().getColor(android.R.color.black));
textSize = tta.getFloat(R.styleable.HSelectView_textSize, 40);
textColor = tta.getColor(R.styleable.HSelectView_textColor, context.getResources().getColor(android.R.color.darker_gray));
tta.recycle();
}
onDraw方法(确定每个item的坐标,然后加上偏移量,这一串东西就可以滑动了,左右任滑)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (firstVisible) {//第一次绘制的时候得到控件 宽高;
width = getWidth();
height = getHeight();
anInt = width / seeSize;
firstVisible = false;
}
if (selectItem >= 0 && selectItem <= stringList.size() - 1) {//加个保护;防止越界
String selectStr = stringList.get(selectItem);//得到被选中的文字
/**
* 得到被选中文字 绘制时所需要的宽高
*/
selectedPaint.getTextBounds(selectStr, 0, selectStr.length(), rect);
//3从矩形区域中读出文本内容的宽高
int centerTextWidth = rect.width();
centerTextHeight = rect.height();
float selectWidth = getWidth() / 2 - centerTextWidth / 2 + anOffset;
selectedPaint.setTextSize(calcTextSize(selectWidth + textWidth / 2));
canvas.drawText(stringList.get(selectItem),
selectWidth,
getHeight() / 2 + centerTextHeight / 2,
selectedPaint);//绘制被选中文字,注意点是y坐标
for (int i = 0; i < stringList.size(); i++) {//遍历strings,把每个地方都绘制出来,
if (selectItem > 0 && selectItem < stringList.size() - 1) {
//这里主要是因为strings数据源的文字长度不一样,为了让被选中两边文字距离中心宽度一样,我们取得左右两个文字长度的平均值
textPaint.getTextBounds(stringList.get(selectItem - 1), 0, stringList.get(selectItem - 1).length(), rect);
int width1 = rect.width();
textPaint.getTextBounds(stringList.get(selectItem + 1), 0, stringList.get(selectItem + 1).length(), rect);
int width2 = rect.width();
textWidth = (width1 + width2) / 2;
}
if (i == 0) {//得到高,高度是一样的,所以无所谓
textPaint.getTextBounds(stringList.get(0), 0, stringList.get(0).length(), rect);
textHeight = rect.height();
}
if (i != selectItem) {
float weith = (i - selectItem) * anInt + getWidth() / 2 - textWidth / 2 + anOffset;
locX[i] = weith;//保存位置
textPaint.setTextSize(calcTextSize(weith + textWidth / 2));
canvas.drawText(stringList.get(i),
weith,
getHeight() / 2 + centerTextHeight / 2 + Math.abs(i - selectItem) * 3,
textPaint);//画出每组文字
} else {
locX[i] = selectWidth;//保存位置
}
}
}
}
onTouchEvent方法(主要确定偏移量多少,偏移到一定位置,切换下一个item选中,哈哈哈哈。。。。)
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("action", "onTouchEvent: " + event.getAction());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();//获得点下去的x坐标
myDownX = event.getX();
break;
case MotionEvent.ACTION_MOVE://复杂的是移动时的判断
float scrollX = event.getX();
anOffset = scrollX - downX;//滑动时的偏移量,用于计算每个是数据源文字的坐标值
if (scrollX > downX) {
//向右滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。
if (scrollX - downX >= anInt / 2) {
if (selectItem > 0) {
anOffset = anOffset - anInt;
selectItem = selectItem - 1;
downX = scrollX - anOffset;
setListener(selectItem);
}
}
} else {
//向左滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。
if (downX - scrollX >= anInt / 2) {
if (selectItem < stringList.size() - 1) {
anOffset = anInt + anOffset;
selectItem = selectItem + 1;
downX = scrollX - anOffset;
setListener(selectItem);
}
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
//抬起手指时,偏移量归零,相当于回弹。
if (Math.abs(myDownX - event.getX()) < 10) {
for (int i = 0; i < stringList.size(); i++) {
if (event.getX() > locX[i] && (i == stringList.size() - 1 || event.getX() < locX[i + 1])) {
selectItem = i;
setListener(selectItem);
break;
}
}
}
anOffset = 0;
invalidate();
break;
default:
break;
}
return super.onTouchEvent(event);
}
全部代码一起贴出:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by ruedy on 2017/5/5.
*/
public class HorizontalselectedView extends View {
private Context context;
private List<String> stringList = new ArrayList<String>();//数据源字符串数组
private Float[] locX;//记录每一个item 的x坐标
private int seeSize = 5;//可见个数
private int anInt;//每个字母所占的大小; 宽度除于可见个数
private TextPaint textPaint;
private boolean firstVisible = true;
private int width;//控件宽度
private int height;//控件高度
private Paint selectedPaint;//被选中文字的画笔
private int selectItem;
private float downX;//位置会改变
private float myDownX;//值不改变,只保存按下x值。
private float anOffset;//滑动时的偏移量,用于计算每个是数据源文字的坐标值
private float selectedTextSize;
private int selectedColor;
private float textSize;
private int textColor;
private Rect rect = new Rect();
private int textWidth = 0;
private int textHeight = 0;
private int centerTextHeight = 0;
public HorizontalselectedView(Context context) {
this(context, null);
}
public HorizontalselectedView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HorizontalselectedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
setWillNotDraw(false);
setClickable(true);
initAttrs(attrs);//初始化属性
initPaint();//初始化画笔
}
/**
* 初始化画笔
*/
private void initPaint() {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(textSize);
textPaint.setColor(textColor);
selectedPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
selectedPaint.setColor(selectedColor);
selectedPaint.setTextSize(selectedTextSize);
}
/**
* 初始化属性
*
* @param attrs
*/
private void initAttrs(AttributeSet attrs) {
TypedArray tta = context.obtainStyledAttributes(attrs,
R.styleable.HSelectView);
//两种字体颜色和字体大小
seeSize = tta.getInteger(R.styleable.HSelectView_ViewSeesize, 5);
selectedTextSize = tta.getFloat(R.styleable.HSelectView_selectedTextSize, 50);
selectedColor = tta.getColor(R.styleable.HSelectView_selectedTextColor, context.getResources().getColor(android.R.color.black));
textSize = tta.getFloat(R.styleable.HSelectView_textSize, 40);
textColor = tta.getColor(R.styleable.HSelectView_textColor, context.getResources().getColor(android.R.color.darker_gray));
tta.recycle();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("action", "onTouchEvent: " + event.getAction());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();//获得点下去的x坐标
myDownX = event.getX();
break;
case MotionEvent.ACTION_MOVE://复杂的是移动时的判断
float scrollX = event.getX();
anOffset = scrollX - downX;//滑动时的偏移量,用于计算每个是数据源文字的坐标值
if (scrollX > downX) {
//向右滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。
if (scrollX - downX >= anInt / 2) {
if (selectItem > 0) {
anOffset = anOffset - anInt;
selectItem = selectItem - 1;
downX = scrollX - anOffset;
setListener(selectItem);
}
}
} else {
//向左滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。
if (downX - scrollX >= anInt / 2) {
if (selectItem < stringList.size() - 1) {
anOffset = anInt + anOffset;
selectItem = selectItem + 1;
downX = scrollX - anOffset;
setListener(selectItem);
}
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
//抬起手指时,偏移量归零,相当于回弹。
if (Math.abs(myDownX - event.getX()) < 10) {
for (int i = 0; i < stringList.size(); i++) {
if (event.getX() > locX[i] && (i == stringList.size() - 1 || event.getX() < locX[i + 1])) {
selectItem = i;
setListener(selectItem);
break;
}
}
}
anOffset = 0;
invalidate();
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (firstVisible) {//第一次绘制的时候得到控件 宽高;
width = getWidth();
height = getHeight();
anInt = width / seeSize;
firstVisible = false;
}
if (selectItem >= 0 && selectItem <= stringList.size() - 1) {//加个保护;防止越界
String selectStr = stringList.get(selectItem);//得到被选中的文字
/**
* 得到被选中文字 绘制时所需要的宽高
*/
selectedPaint.getTextBounds(selectStr, 0, selectStr.length(), rect);
//3从矩形区域中读出文本内容的宽高
int centerTextWidth = rect.width();
centerTextHeight = rect.height();
float selectWidth = getWidth() / 2 - centerTextWidth / 2 + anOffset;
selectedPaint.setTextSize(calcTextSize(selectWidth + textWidth / 2));
canvas.drawText(stringList.get(selectItem),
selectWidth,
getHeight() / 2 + centerTextHeight / 2,
selectedPaint);//绘制被选中文字,注意点是y坐标
for (int i = 0; i < stringList.size(); i++) {//遍历strings,把每个地方都绘制出来,
if (selectItem > 0 && selectItem < stringList.size() - 1) {
//这里主要是因为strings数据源的文字长度不一样,为了让被选中两边文字距离中心宽度一样,我们取得左右两个文字长度的平均值
textPaint.getTextBounds(stringList.get(selectItem - 1), 0, stringList.get(selectItem - 1).length(), rect);
int width1 = rect.width();
textPaint.getTextBounds(stringList.get(selectItem + 1), 0, stringList.get(selectItem + 1).length(), rect);
int width2 = rect.width();
textWidth = (width1 + width2) / 2;
}
if (i == 0) {//得到高,高度是一样的,所以无所谓
textPaint.getTextBounds(stringList.get(0), 0, stringList.get(0).length(), rect);
textHeight = rect.height();
}
if (i != selectItem) {
float weith = (i - selectItem) * anInt + getWidth() / 2 - textWidth / 2 + anOffset;
locX[i] = weith;//保存位置
textPaint.setTextSize(calcTextSize(weith + textWidth / 2));
canvas.drawText(stringList.get(i),
weith,
getHeight() / 2 + centerTextHeight / 2 + Math.abs(i - selectItem) * 3,
textPaint);//画出每组文字
} else {
locX[i] = selectWidth;//保存位置
}
}
}
}
/**
* 根据到中间位置的距离大小,确定字体的大小
*
* @param width
* @return
*/
private float calcTextSize(float width) {
float size = selectedTextSize - textSize;//最大字体和最小字体差距
float bilv = 1 - Math.abs(width - getWidth() / 2) / (getWidth() / 2);
return textSize + size * bilv;
}
/**
* 改变中间可见文字的数目
*
* @param seeSizes 可见数
*/
public void setSeeSize(int seeSizes) {
if (seeSize > 0) {
seeSize = seeSizes;
invalidate();
}
}
/**
* 向左移动一个单元
*/
public void setAnLeftOffset() {
if (selectItem < stringList.size() - 1) {
selectItem = selectItem + 1;
invalidate();
}
}
/**
* 向右移动一个单元
*/
public void setAnRightOffset() {
if (selectItem > 0) {
selectItem = selectItem - 1;
invalidate();
}
}
/**
* 设置个数据源
*
* @param strings 数据源String集合
*/
public void setData(List<String> strings) {
this.stringList = strings;
locX = new Float[strings.size()];
selectItem = strings.size() / 2;
setListener(selectItem);
invalidate();
}
/**
* 获得被选中的文本
*
* @return 被选中的文本
*/
public String getSelectedString() {
if (stringList.size() != 0) {
return stringList.get(selectItem);
}
return null;
}
private void setListener(int selectItem) {
if (listener != null) {
listener.itemSelect(selectItem);
}
}
private OnItemSelectListener listener;
public void setOnItemSelectListener(OnItemSelectListener listener) {
this.listener = listener;
}
public interface OnItemSelectListener {
void itemSelect(int item);
}
}
使用方式:
<com.iszcc.demo.carview.HorizontalselectedView
android:id="@+id/hd_main"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="30"
app:ViewSeesize="5"
app:selectedTextColor="#f00"
app:selectedTextSize="60"
app:textColor="#0ff"
app:textSize="40" />
监听:
hsMain.setOnItemSelectListener(new HorizontalselectedView.OnItemSelectListener() {
@Override
public void itemSelect(int item) {
tvMain.setText(strings.get(item));
}
});
上一个、下一个
hsMain.setAnLeftOffset();
hsMain.setAnRightOffset();