編輯:關於Android編程
實現power by:https://dribbble.com/LeslyPyram
先上下原設計:
vcTEwcso1eLKx87S19S8usLytcS2q873tcS92M28o6y/tMq1z9a+zbrDKTwvcD4NCjxwPtTZzPnPwqOsvfHM7Mq1z9a1xNCnufujujwvcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160621/20160621091005819.gif" title="\" />
顏色不管,別的大致就差不多“翻版了”(間距和球體空心實心只是個人愛好問題,這不是模仿難點)
來看下項目目錄
東西很簡單,就一個控件就能搞定,先來看看如何使用
在布局文件裡設置下總的“進程數”就可以了。
如果你要設置球的顏色,大小,邊框粗細,如下:
app:stpi_circleColor
app:stpi_circleRadius
app:stpi_circleStrokeWidth
當然,你也有其他的自定義選項,這裡不做全部介紹了,你可以看 attrs.xml文件內的標簽,命名很規范,一看就懂
看完怎麼用,來看下怎麼實現的
public class MyStepperIndicator extends View {
private static final float EXPAND_MARK = 1.3f;
private static final int DEFAULT_ANIMATION_DURATION = 250;
private Resources resources;
private Paint circlePaint;
private Paint linePaint, lineDonePaint, lineDoneAnimatedPaint;
private Paint indicatorPaint;
private float circleRadius;
private float animProgress;
private float checkRadius;
private float animIndicatorRadius;
private float indicatorRadius;
private float lineLength;
private float lineMargin;
private float animCheckRadius;
private int animDuration;
private int stepCount;
private int currentStep;
private Bitmap doneIcon;
private float[] indicators;
private List linePathList = new ArrayList<>();
private AnimatorSet animatorSet;
private ObjectAnimator lineAnimator, indicatorAnimator, checkAnimator;
private int previousStep;
//構造函數
public MyStepperIndicator(Context context) {
this(context, null);
}
public MyStepperIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyStepperIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MyStepperIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
//獲取項目資源用
resources = getResources();
// 默認值
int defaultCircleColor = ContextCompat.getColor(context, R.color.stpi_default_circle_color);
float defaultCircleRadius = resources.getDimension(R.dimen.stpi_default_circle_radius);
float defaultCircleStrokeWidth = resources.getDimension(R.dimen.stpi_default_circle_stroke_width);
int defaultIndicatorColor = ContextCompat.getColor(context, R.color.stpi_default_indicator_color);
float defaultIndicatorRadius = resources.getDimension(R.dimen.stpi_default_indicator_radius);
float defaultLineStrokeWidth = resources.getDimension(R.dimen.stpi_default_line_stroke_width);
float defaultLineMargin = resources.getDimension(R.dimen.stpi_default_line_margin);
int defaultLineColor = ContextCompat.getColor(context, R.color.stpi_default_line_color);
int defaultLineDoneColor = ContextCompat.getColor(context, R.color.stpi_default_line_done_color);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StepperIndicator, defStyleAttr, 0);
circlePaint = new Paint();
circlePaint.setStrokeWidth(a.getDimension(R.styleable.StepperIndicator_stpi_circleStrokeWidth, defaultCircleStrokeWidth));
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_circleColor, defaultCircleColor));
circlePaint.setAntiAlias(true);
indicatorPaint = new Paint(circlePaint);
indicatorPaint.setStyle(Paint.Style.FILL);
indicatorPaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_indicatorColor, defaultIndicatorColor));
indicatorPaint.setAntiAlias(true);
linePaint = new Paint();
linePaint.setStrokeWidth(a.getDimension(R.styleable.StepperIndicator_stpi_lineStrokeWidth, defaultLineStrokeWidth));
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineColor, defaultLineColor));
linePaint.setAntiAlias(true);
lineDonePaint = new Paint(linePaint);
lineDonePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineDoneColor, defaultLineDoneColor));
lineDoneAnimatedPaint = new Paint(lineDonePaint);
circleRadius = a.getDimension(R.styleable.StepperIndicator_stpi_circleRadius, defaultCircleRadius);
checkRadius = circleRadius + circlePaint.getStrokeWidth() / 2f;
indicatorRadius = a.getDimension(R.styleable.StepperIndicator_stpi_indicatorRadius, defaultIndicatorRadius);
animIndicatorRadius = indicatorRadius;
animCheckRadius = checkRadius;
lineMargin = a.getDimension(R.styleable.StepperIndicator_stpi_lineMargin, defaultLineMargin);
setStepCount(a.getInteger(R.styleable.StepperIndicator_stpi_stepCount, 2));
animDuration = a.getInteger(R.styleable.StepperIndicator_stpi_animDuration, DEFAULT_ANIMATION_DURATION);
a.recycle();
doneIcon = BitmapFactory.decodeResource(resources, R.drawable.ic_done_white_18dp);
if (isInEditMode())
currentStep = Math.max((int) Math.ceil(stepCount / 2f), 1);
}
public void setStepCount(int stepCount) {
if (stepCount < 2)
throw new IllegalArgumentException("stepCount must be >= 2");
this.stepCount = stepCount;
currentStep = 0;
compute();
invalidate();
}
private void compute() {
indicators = new float[stepCount];
linePathList.clear();
float startX = circleRadius * EXPAND_MARK + circlePaint.getStrokeWidth() / 2f;
// 計算指標和線路長度的位置
float divider = (getMeasuredWidth() - startX * 2f) / (stepCount - 1);
lineLength = divider - (circleRadius * 2f + circlePaint.getStrokeWidth()) - (lineMargin * 2);
// 計算圈和線的位置
for (int i = 0; i < indicators.length; i++)
indicators[i] = startX + divider * i;
for (int i = 0; i < indicators.length - 1; i++) {
float position = ((indicators[i] + indicators[i + 1]) / 2) - lineLength / 2;
final Path linePath = new Path();
linePath.moveTo(position, getMeasuredHeight() / 2);
linePath.lineTo(position + lineLength, getMeasuredHeight() / 2);
linePathList.add(linePath);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d("--->onSizeChanged", "w = " + w + " h = " + h + " oldw = " + oldw + " oldh = " + oldh);
compute();
}
//控件繪圖
@SuppressWarnings("ConstantConditions")
@Override
protected void onDraw(Canvas canvas) {
float centerY = getMeasuredHeight() / 2f;
// 目前繪制動畫步驟n-1 n,或從n + 1到n
boolean inAnimation = false;
boolean inLineAnimation = false;
boolean inIndicatorAnimation = false;
boolean inCheckAnimation = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
inAnimation = animatorSet != null && animatorSet.isRunning();
inLineAnimation = lineAnimator != null && lineAnimator.isRunning();
inIndicatorAnimation = indicatorAnimator != null && indicatorAnimator.isRunning();
inCheckAnimation = checkAnimator != null && checkAnimator.isRunning();
}
boolean drawToNext = previousStep == currentStep - 1;
boolean drawFromNext = previousStep == currentStep + 1;
for (int i = 0; i < indicators.length; i++) {
final float indicator = indicators[i];
boolean drawCheck = i < currentStep || (drawFromNext && i == currentStep);
// 畫圈
canvas.drawCircle(indicator, centerY, circleRadius, circlePaint);
// 如果當前步驟,或回來,下一步還有返回動畫
if ((i == currentStep && !drawFromNext) || (i == previousStep && drawFromNext && inAnimation)) {
// Draw animated indicator
canvas.drawCircle(indicator, centerY, animIndicatorRadius, indicatorPaint);
}
// 畫對勾
if (drawCheck) {
float radius = checkRadius;
if ((i == previousStep && drawToNext)
|| (i == currentStep && drawFromNext))
radius = animCheckRadius;
canvas.drawCircle(indicator, centerY, radius, indicatorPaint);
if (!isInEditMode()) {
if ((i != previousStep && i != currentStep) || (!inCheckAnimation && !(i == currentStep && !inAnimation)))
canvas.drawBitmap(doneIcon, indicator - (doneIcon.getWidth() / 2), centerY - (doneIcon.getHeight() / 2), null);
}
}
// 畫線
if (i < linePathList.size()) {
if (i >= currentStep) {
canvas.drawPath(linePathList.get(i), linePaint);
if (i == currentStep && drawFromNext && (inLineAnimation || inIndicatorAnimation)) // Coming back from n+1
canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint);
} else {
if (i == currentStep - 1 && drawToNext && inLineAnimation) {
// Going to n+1
canvas.drawPath(linePathList.get(i), linePaint);
canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint);
} else
canvas.drawPath(linePathList.get(i), lineDonePaint);
}
}
}
}
//控件尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredHeight = (int) Math.ceil((circleRadius * EXPAND_MARK * 2) + circlePaint.getStrokeWidth());
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = widthMode == MeasureSpec.EXACTLY ? widthSize : getSuggestedMinimumWidth();
int height = heightMode == MeasureSpec.EXACTLY ? heightSize : desiredHeight;
setMeasuredDimension(width, height);
}
public int getStepCount() {
return stepCount;
}
public int getCurrentStep() {
return currentStep;
}
/**
* 設置現在的位置
*
* @param currentStep a value between 0 (inclusive) and stepCount (inclusive)
*/
@UiThread
public void setCurrentStep(int currentStep) {
if (currentStep < 0 || currentStep > stepCount)
throw new IllegalArgumentException("Invalid step value " + currentStep);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (animatorSet != null)
animatorSet.cancel();
animatorSet = null;
lineAnimator = null;
indicatorAnimator = null;
if (currentStep == this.currentStep + 1) {
previousStep = this.currentStep;
animatorSet = new AnimatorSet();
// 畫新的線
lineAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animProgress", 1.0f, 0.0f);
// 標記
checkAnimator = ObjectAnimator
.ofFloat(MyStepperIndicator.this, "animCheckRadius", indicatorRadius, checkRadius * EXPAND_MARK, checkRadius);
// 最後到達目標"進度"
animIndicatorRadius = 0;
indicatorAnimator = ObjectAnimator
.ofFloat(MyStepperIndicator.this, "animIndicatorRadius", 0f, indicatorRadius * 1.4f, indicatorRadius);
animatorSet.play(lineAnimator).with(checkAnimator).before(indicatorAnimator);
} else if (currentStep == this.currentStep - 1) {
//回退操作
previousStep = this.currentStep;
animatorSet = new AnimatorSet();
indicatorAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animIndicatorRadius", indicatorRadius, 0f);
animProgress = 1.0f;
lineDoneAnimatedPaint.setPathEffect(null);
lineAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animProgress", 0.0f, 1.0f);
animCheckRadius = checkRadius;
checkAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animCheckRadius", checkRadius, indicatorRadius);
animatorSet.playSequentially(indicatorAnimator, lineAnimator, checkAnimator);
}
if (animatorSet != null) {
lineAnimator.setDuration(Math.min(500, animDuration));
lineAnimator.setInterpolator(new DecelerateInterpolator());
indicatorAnimator.setDuration(lineAnimator.getDuration() / 2);
checkAnimator.setDuration(lineAnimator.getDuration() / 2);
animatorSet.start();
}
}
this.currentStep = currentStep;
invalidate();
}
public void setAnimIndicatorRadius(float animIndicatorRadius) {
this.animIndicatorRadius = animIndicatorRadius;
invalidate();
}
public void setAnimCheckRadius(float animCheckRadius) {
this.animCheckRadius = animCheckRadius;
invalidate();
}
}
重要步驟已經寫在裡面了,這裡做下詳細解釋
1.初始化一系列需要的參數(從xml獲得或者通過set方法)
2.位置計算
3.在draw方法裡根據獲取的參數進行一系列的繪制,繪制內容為 點,圓 線。
然後就是一系列邏輯和動畫了(實現概念很基礎,但是基礎很重要)
setCurrentStep方法為最為關鍵的動畫實現方法。根據當前“進度”值currentStep作為邏輯判斷的標准。
源碼地址:https://github.com/ddwhan0123/BlogSample/blob/master/StepperIndicatorDemo/StepperIndicatorDemo.zip?raw=true
關於使用MarkDown編輯器的原因 其實前段時間就支持使用MarkDown編輯器來寫博客了,只是由於當時接觸過MarkDown,所以之前的博客都是使用默認的HTML編輯
我們在一些項目中會用到自定義流式布局,我個人覺得流式布局將呆板的布局錯綜排列,來提升用戶體驗度.(還可以不辜負美工妹子們的期望,人家畢竟也辛辛苦苦設計半天)。今天終於有時
雖然之前也分析過View回執過程,但是如果讓我自己集成ViewGroup然後自己重新onMeasure,onLayout,onDraw方法自定義View我還是會頭疼。今天
首先來看一下效果圖; 先說一下我的需求:查看群成員,如果超過15人則全部顯示,如果大於15人則先加載15人,其余的不顯示,點擊查看更多則加載全部。再來說一下我