转载请注明原创出处,谢谢!
- GitHub: @Ricco

效果图
ps:最近做一个字体放大的需求,有一个进度条的效果使用到了自定义view
需求:
- 可滑动
- 进度为0时,左边显示0,右边显示“大”
- 进度为5时,左边显示“小”,右边显示5
- 进度为1-4时,左边显示“小”,右边显示“大”,中间进度位置显示具体数额
- 触摸时,显示距离触摸位置最近的进度
实现思路:
- 整体使用FrameLayout,并设置背景色
- 里面放左右2个TextView(任意view,也可以是图片),用来显示”小“和”大“
- 自定义ViewGroup(即MySeekBar),处理触摸事件,控制MySeekBar的第一子View(TextView)位置达到效果
- MySeekBar只处理位置,具体显示内容,通过监听事件交给页面自己处理,例如下面Activity实例代码
package com.example.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* 自定义字体放大进度条ViewGroup
*
* @author ricco
*/
public class FontSizeSeekBar extends ViewGroup {
/**
* 当前进度
*/
private int mCurProgress = 0;
/**
* 最大进度
*/
private int mMaxProgress = 5;
/**
* 每个进度点的位置
*/
private float[] touchPoint = new float[mMaxProgress + 1];
/**
* 2个进度点中心的的距离
*/
private float step = 0;
private int w, h;
public FontSizeSeekBar(Context context) {
super(context);
}
public FontSizeSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
w = MeasureSpec.getSize(widthMeasureSpec);
h = MeasureSpec.getSize(heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
init();
}
private void init() {
// 2个圆心的位置
step = 1.0f * (w - h) / mMaxProgress;
touchPoint = new float[mMaxProgress + 1];
for (int i = 0; i < touchPoint.length; i++) {
touchPoint[i] = h / 2.f + step * i;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
layoutProgressByCur();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸x的指标位置,计算进度
int progress = calculateProgressByEventX(touchPoint, event.getX());
if (mCurProgress != progress) {
mCurProgress = progress;
// 重新摆明
layoutProgressByCur();
}
return true;
}
/**
* 根据当前进度更新显示
*/
public void layoutProgressByCur() {
View child = getChildAt(0);
if (child != null) {
if (mCurProgress <= 0) {
// 小于等于0的,全部按照0处理
child.layout(0, 0, h, h);
} else if (mCurProgress >= mMaxProgress) {
// 大于等于最大进度的,全部按照最大进度处理
child.layout(w - h, 0, w, h);
} else {
child.layout((int) (step * mCurProgress), 0, (int) (step * mCurProgress) + h, h);
}
if (mListener != null) {
mListener.onProgressChanged(child, mCurProgress);
}
}
}
/**
* 设置最前值
* <p>
* setMaxProgress或setCurProgress后,最好手动调用layoutProgressByCur
*/
public void setCurProgress(int curProgress) {
if (curProgress < 0) {
curProgress = 0;
}
this.mCurProgress = Math.min(curProgress, mMaxProgress);
}
public int getCurProgress() {
return Math.min(mCurProgress, mMaxProgress);
}
/**
* 设置最大值
* <p>
* setMaxProgress或setCurProgress后,最好手动调用layoutProgressByCur
*/
public void setMaxProgress(int maxProgress) {
if (mCurProgress > maxProgress) {
// 必须先调小当前进度,再调最大进度
return;
}
this.mMaxProgress = maxProgress;
// mCurProgress会印象当前展示样式,需要重新计算位置
init();
}
// Util=========================================================================================
/**
* 根据触摸x的指标位置,计算进度
* <p>
* 获取一个数的最近的位置
*
* @param arr 数组
* @param num 数
* @return 最近的位置
*/
private int calculateProgressByEventX(float[] arr, float num) {
if (arr != null && arr.length > 0) {
float[] newArr = new float[arr.length];
// 最小的绝对值,默认第一最小
float newArrMin = Math.abs(arr[0] - num);
for (int i = 0; i < arr.length; i++) {
// 差的绝对值
newArr[i] = Math.abs(arr[i] - num);
newArrMin = Math.min(newArr[i], newArrMin);
}
// 最小绝对值的位置,就是最近的点
for (int i = 0; i < newArr.length; i++) {
if (newArr[i] == newArrMin) {
return i;
}
}
}
return 0;
}
// Listener=====================================================================================
/**
* 滑动监听事件
*/
private OnFontSizeSeekBarChangeListener mListener;
public void setOnSeekBarChangeListener(OnFontSizeSeekBarChangeListener onListener) {
this.mListener = onListener;
}
public interface OnFontSizeSeekBarChangeListener {
/**
* 进度发生变化的回调接口
*
* @param childView 滑动的子view
* @param curProgress 当前进度
*/
void onProgressChanged(View childView, int curProgress);
}
}
示例代码
MainActivity
public class MainActivity extends AppCompatActivity
implements MySeekBar.OnFontSizeSeekBarChangeListener{
private FontSizeSeekBar mySeekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySeekBar = findViewById(R.id.mySeekBar);
mySeekBar.setListener(this);
mySeekBar.setMaxProgress(5);
mySeekBar.setCurProgress(0);
// layoutProgressByCur 会重新摆放view,并且回调监听
mySeekBar.layoutProgressByCur();
}
@Override
public void onListener(View childView, int curProgress) {
if (childView instanceof TextView) {
TextView textView = (TextView) childView;
String scaleStr = progressToScaleStr(curProgress);
textView.setText(scaleStr);
updateUi(Double.parseDouble(scaleStr));
}
}
/**
* 进度转显示字符串
*/
private String progressToScaleStr(int progress) {
double scale = 1 + progress / 10.0;
DecimalFormat decimalFormat = new DecimalFormat("0.0");
decimalFormat.setRoundingMode(RoundingMode.UP);
return decimalFormat.format(scale);
}
}
MainActivity布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_margin="20dp"
android:background="@drawable/bg_f4f5f7">
<TextView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="start"
android:gravity="center"
android:text="小"
android:textSize="14sp" />
<TextView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end"
android:gravity="center"
android:text="大"
android:textSize="18sp" />
<com.example.myapplication.FontSizeSeekBar
android:id="@+id/mySeekBar"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="32dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@drawable/bg_737780"
android:gravity="center"
android:text="1"
android:textColor="#FFFFFF"
android:textSize="14sp" />
</com.example.myapplication.FontSizeSeekBar>
</FrameLayout>
</LinearLayout>
选中样式
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#737780" />
<corners android:radius="1000dp" />
</shape>
背景样式
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#f4f5f7" />
<corners android:radius="1000dp" />
</shape>