这篇文章主要实现如下效果,这实现的过程中,大家可以发现使用了一些数学里面的计算公式,其实特效是和数学知识分不开的
先介绍下这个特效的组成部分,这个特效有上下半区组成;
1:上半区主要显示当前年月日,星期几
2:下半区的红线表示时针,一个个的小圆表示单一的事件,每一个事件有起始时间和结束时间,我们会计算出起始时间和结束时间对应表盘的位置,然后画弧,并且填入事件的内容
以下是特效的代码,代码里面有比较详尽的解释
主Activity
package com.hwj.android.learning;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import java.util.Calendar;
public class ArcMainActivity extends Activity{
private String[] monthStringValues;
private String[] dayOfWeekValues;
private TextView txtYear;
private TextView txtMonth;
private TextView txtDayOfWeek;
private TextView txtDate;
private Animation txtDateAnim;
private Animation txtYearAnim;
private Animation txtMonthAnim;
private Animation txtDayOfWeekAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_arc_main_layout);
initSetting();
}
private void initSetting() {
monthStringValues = this.getResources().getStringArray(R.array.month_type_array);
dayOfWeekValues = this.getResources().getStringArray(R.array.day_of_week_type_array);
txtYear = (TextView) this.findViewById(R.id.txt_year);
txtMonth = (TextView) this.findViewById(R.id.txt_month);
txtDayOfWeek = (TextView) this.findViewById(R.id.txt_day_of_week);
txtDate = (TextView) this.findViewById(R.id.txt_date);
txtDateAnim = AnimationUtils.loadAnimation(this, R.anim.scale_0_to_1);
txtYearAnim = AnimationUtils.loadAnimation(this, R.anim.bottom_to_top_slide_in);
txtMonthAnim = AnimationUtils.loadAnimation(this, R.anim.bottom_to_top_slide_in);
txtDayOfWeekAnim = AnimationUtils.loadAnimation(this, R.anim.bottom_to_top_slide_in);
txtDateAnim.setAnimationListener(new AnimationHandler(txtYear, txtYearAnim));
txtYearAnim.setAnimationListener(new AnimationHandler(txtMonth, txtMonthAnim));
txtMonthAnim.setAnimationListener(new AnimationHandler(txtDayOfWeek, txtDayOfWeekAnim));
txtDayOfWeekAnim.setAnimationListener(new AnimationHandler(null, null));
Calendar calendar = Calendar.getInstance();
txtYear.setText(String.valueOf(calendar.get(Calendar.YEAR)));
txtMonth.setText(monthStringValues[calendar.get(Calendar.MONTH)]);
txtDayOfWeek.setText(dayOfWeekValues[calendar.get(Calendar.DAY_OF_WEEK) - 1]);
txtDate.setText(String.valueOf(calendar.get(Calendar.DATE)));
txtDate.startAnimation(txtDateAnim);
}
private class AnimationHandler implements Animation.AnimationListener {
private TextView nextTextView;
private Animation nextAnimation;
public AnimationHandler(TextView nextTextView, Animation nextAnimation) {
this.nextTextView = nextTextView;
this.nextAnimation = nextAnimation;
}
@Override
public void onAnimationEnd(Animation animation) {
if (nextTextView != null && nextAnimation != null) {
nextTextView.startAnimation(nextAnimation);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
}
}
}
主activity的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@android:color/white"
android:text="0000"
android:layout_centerHorizontal="true"
android:visibility="visible"/>
<TextView
android:id="@+id/txt_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9px"
android:layout_below="@+id/txt_year"
android:gravity="center"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:visibility="visible"/>
<TextView
android:id="@+id/txt_day_of_week"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5px"
android:layout_below="@+id/txt_month"
android:gravity="center"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:visibility="visible"/>
<TextView
android:id="@+id/txt_date"
android:layout_width="50px"
android:layout_height="50px"
android:layout_marginTop="5px"
android:layout_below="@+id/txt_day_of_week"
android:gravity="center"
android:textColor="@android:color/black"
android:text="3"
android:layout_centerHorizontal="true"
android:background="@drawable/white_background_circle"
android:visibility="visible"/>
<com.hwj.android.learning.TimeTableClockView
android:id="@+id/time_table_clock_view"
android:layout_below="@+id/txt_date"
android:layout_width="500px"
android:layout_height="500px"
android:layout_marginTop="20px"
android:layout_centerHorizontal="true"
android:background="@drawable/clock"/>
</RelativeLayout>
构建下半区的自定义view
package com.hwj.android.learning;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.FrameLayout;
public class TimeTableClockView extends FrameLayout implements AnimationEndListener {
private Paint circlePaint;
private Paint hourBandPaint;
private Path linePath;
private int circleCount = 0;
private int width, height;
private Handler uiUpdateHandler;
private final int EVENT_CIRCLE = 0;
private final int EVENT_HOUR_BAND = 1;
private final int EVENT_CIRCLE_ANIMATION_ENDCHECK = 2;
private final int EVENT_RECT_ANIMATION_ENDCHECK = 3;
private final int EVENT_CIRCLE_INTERVAL = 100;
private final int MAX_NUM_EVENT_CIRCLE = 18;
//圆环基数
public static final float EVENT_CIRCLE_SIZE = 13f;
private final int EVENT_HOUR_BAND_INTERVAL = 1000;
private int centerX, centerY, hourLineLength;
//该弧度用于绘制时针
private double curRadianAngle;
//根据图片一共24小时,则一个时钟供24段,顾每段的角度为15度,每段代表一个小时,顾每秒的角度为15/3600 = 0.00416666666666度
private final double DEGREE_FOR_ONESEC = 0.00416666666666; // 15 degree / 3600 sec
private ArrayList<ArcTextView> scheduleView;
private final int CIRCLE_ANIMATION_DURATION = 1500; // duration to show circle animation
private final int RECT_ANIMATION_DURATION = 1500; // duration to show rect animation
private List<Event> todayEventList;
private List<ArcTextViewData> scheduleDataList;
private long todayStartTime, todayEndTime;
public TimeTableClockView(Context context) {
super(context);
initSetting();
}
public TimeTableClockView(Context context, AttributeSet attrs) {
super(context, attrs);
initSetting();
}
private void initSetting() {
//每一圈的圆圈的颜色
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setColor(Color.DKGRAY);
circlePaint.setStrokeWidth(1);
circlePaint.setDither(true);
circlePaint.setStyle(Paint.Style.STROKE);
//时针的颜色
hourBandPaint = new Paint();
hourBandPaint.setStyle(Paint.Style.STROKE);
hourBandPaint.setStrokeCap(Paint.Cap.ROUND);
hourBandPaint.setStrokeWidth(3.0f);
hourBandPaint.setDither(true);
hourBandPaint.setAntiAlias(true);
hourBandPaint.setColor(Color.RED);
//时针的path
linePath = new Path();
//装载弧形textview的list
scheduleView = new ArrayList<ArcTextView>();
uiUpdateHandler = new Handler() {
public void handleMessage(Message msg) {
onHandleMessage(msg);
}
};
uiUpdateHandler.sendEmptyMessageDelayed(EVENT_CIRCLE, 500);
Calendar calendar = Calendar.getInstance();
int todayYear = calendar.get(Calendar.YEAR);
int todayMonth = calendar.get(Calendar.MONTH);
int todayDate = calendar.get(Calendar.DATE);
calendar.clear();
calendar.set(todayYear, todayMonth, todayDate);
todayStartTime = calendar.getTimeInMillis();
todayEndTime = todayStartTime + 3600 * 24 * 1000;
scheduleDataList = new ArrayList<ArcTextViewData>();
initTodayEventList();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = right - left;
height = bottom - top;
//计算圆心
centerX = width / 2;
centerY = height / 2;
//计算时针的长度
hourLineLength = centerY - 25;
}
public void updateCircle() {
circleCount++;
invalidate();
}
public void updateHourBand() {
curRadianAngle = getCurrentTimeAngle();
invalidate();
}
private double getCurrentTimeAngle() {
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int min = calendar.get(Calendar.MINUTE);
int sec = calendar.get(Calendar.SECOND);
long curTimeSecs = (hour * 3600) + (min * 60) + sec;
double degreeAngle = curTimeSecs * DEGREE_FOR_ONESEC;
//计算对应时间的弧度,即将角度转换为弧度,这个弧度用于计算时针末端的位置
double radianAngle = (degreeAngle - 90) * Math.PI / 180;
return radianAngle;
}
private void initTodayEventList() {
curRadianAngle = getCurrentTimeAngle();
todayEventList = new ArrayList<Event>();
Calendar calendar = Calendar.getInstance();
int todayYear = calendar.get(Calendar.YEAR);
int todayMonth = calendar.get(Calendar.MONTH);
int todayDate = calendar.get(Calendar.DATE);
calendar.clear();
calendar.set(todayYear, todayMonth, todayDate);
//开始时间 = 当前时间
long nowMillSeconds = calendar.getTimeInMillis();
//结束时间 = 当前时间 + 10分钟
long endMillSeconds = calendar.getTimeInMillis() + 60 * 1000 * 10;
Event event1 = new Event("title1", false, nowMillSeconds, endMillSeconds);
Event event2 = new Event("title2", false, nowMillSeconds + 60 * 1000 * 5, endMillSeconds + 60 * 1000 * 60);
Event event4 = new Event("title3", false, nowMillSeconds + 60 * 1000 * 10, endMillSeconds + 60 * 1000 * 60 * 2);
Event event5 = new Event("title4", false, nowMillSeconds + 60 * 1000 * 20, endMillSeconds + 60 * 1000 * 60 * 4);
Event event7 = new Event("title5", false, nowMillSeconds + 60 * 1000 * 30, endMillSeconds + 60 * 1000 * 60 * 6);
Event event8 = new Event("title6", false, nowMillSeconds + 60 * 1000 * 35, endMillSeconds + 60 * 1000 * 60 * 8);
Event event6 = new Event("title7", true, nowMillSeconds, endMillSeconds);
Event event3 = new Event("title8", true, nowMillSeconds, endMillSeconds);
event1.color = Color.parseColor("#8000FF");
event2.color = Color.parseColor("#F6F6F6");
event3.color = Color.BLUE;
event4.color = Color.parseColor("#4BB5DD");
event5.color = Color.parseColor("#4C4C4C");
event6.color = Color.YELLOW;
event7.color = Color.parseColor("#57D2FF");
event8.color = Color.parseColor("#E8E8E8");
todayEventList.add(event1);
todayEventList.add(event2);
todayEventList.add(event3);
todayEventList.add(event4);
todayEventList.add(event5);
todayEventList.add(event6);
todayEventList.add(event7);
todayEventList.add(event8);
}
private void addArcTextViews() {
for (int i = 0; i < todayEventList.size(); i++) {
Event event = todayEventList.get(i);
ArcTextViewData scheduleData = new ArcTextViewData();
//事件开始时间
scheduleData.startTimeSec = (event.startMillis - todayStartTime) / 1000;
//事件结束时间
scheduleData.endTimeSec = (event.endMillis - todayStartTime) / 1000;
if (event.allDay) {
scheduleData.startTimeSec = 0;
scheduleData.endTimeSec = 3600 * 24;
}
scheduleData.backgroundColor = event.color;
scheduleData.scheduleTitle = event.title.toString();
scheduleDataList.add(scheduleData);
}
for (int index = 0; index < scheduleDataList.size(); index++) {
ArcTextViewData data = scheduleDataList.get(index);
data.radiusIn = EVENT_CIRCLE_SIZE * index;
data.radiusOut = data.radiusIn + EVENT_CIRCLE_SIZE * 3;
}
for (int i = 0; i < scheduleDataList.size(); i++) {
ArcTextView arcTextView = new ArcTextView(getContext());
arcTextView.setInitParameter(scheduleDataList.get(i));
arcTextView.setAnimationEndListener(this);
scheduleView.add(arcTextView);
}
if (scheduleView.size() > 0) {
int delayAddArcTextView = CIRCLE_ANIMATION_DURATION / scheduleView.size();
for (int i = 0; i < scheduleView.size(); i++) {
final ArcTextView arcTextView = scheduleView.get(i);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
arcTextView.setLayoutParams(lp);
postDelayed(new Runnable() {
public void run() {
addView(arcTextView);
}
}, i * delayAddArcTextView);
}
}
}
@Override
public void onAnimationEnd(int type) {
if (uiUpdateHandler != null) {
if (type == AnimationEndListener.TYPE_CIRCLE) {
uiUpdateHandler.removeMessages(EVENT_CIRCLE_ANIMATION_ENDCHECK);
uiUpdateHandler.sendEmptyMessage(EVENT_CIRCLE_ANIMATION_ENDCHECK);
} else if (type == AnimationEndListener.TYPE_RECT) {
uiUpdateHandler.removeMessages(EVENT_RECT_ANIMATION_ENDCHECK);
uiUpdateHandler.sendEmptyMessage(EVENT_RECT_ANIMATION_ENDCHECK);
}
}
}
//圆形渐变动画结束,环形文字动画开始
private void checkCircleAnimationIsEnded() {
int isFinishedCount = 0;
for (ArcTextView textView : scheduleView) {
if (textView.isCircleAnimationMode() == false)
isFinishedCount++;
}
if (scheduleView.size() == isFinishedCount) {
int delayStartArcAnimation = RECT_ANIMATION_DURATION / scheduleView.size();
for (int i = 0; i < scheduleView.size(); i++) {
final ArcTextView arcTextView = scheduleView.get(i);
postDelayed(new Runnable() {
public void run() {
arcTextView.startRectAnimation();
}
}, i * delayStartArcAnimation);
}
}
}
//所有动画结束之后,动态刷新时针的位置
private void checkRectAnimationIsEnded() {
int isFinishedCount = 0;
for (ArcTextView textView : scheduleView) {
if (textView.isRectAnimationMode() == false)
isFinishedCount++;
}
if (scheduleView.size() == isFinishedCount) {
uiUpdateHandler.sendEmptyMessageDelayed(EVENT_HOUR_BAND, EVENT_CIRCLE_INTERVAL);
}
}
private void onHandleMessage(Message msg) {
switch (msg.what) {
case EVENT_CIRCLE: {
updateCircle();
//绘制圆环和时针
if (circleCount < MAX_NUM_EVENT_CIRCLE)
uiUpdateHandler.sendEmptyMessageDelayed(EVENT_CIRCLE, EVENT_CIRCLE_INTERVAL);
else //添加事件
if (circleCount == MAX_NUM_EVENT_CIRCLE) {
addArcTextViews();
}
break;
}
case EVENT_HOUR_BAND: {
updateHourBand();
uiUpdateHandler.sendEmptyMessageDelayed(EVENT_HOUR_BAND, EVENT_HOUR_BAND_INTERVAL);
break;
}
case EVENT_CIRCLE_ANIMATION_ENDCHECK: {
checkCircleAnimationIsEnded();
break;
}
case EVENT_RECT_ANIMATION_ENDCHECK: {
checkRectAnimationIsEnded();
break;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < circleCount; i++) {
canvas.drawCircle(centerX, centerY, EVENT_CIRCLE_SIZE * (MAX_NUM_EVENT_CIRCLE - i), circlePaint);
}
//根据弧长和起点坐标计算终点坐标
int endX = (int) (centerX + hourLineLength * Math.cos(curRadianAngle));
int endY = (int) (centerY + hourLineLength * Math.sin(curRadianAngle));
linePath.reset();
linePath.moveTo(centerX, centerY);
linePath.lineTo(endX, endY);
canvas.drawPath(linePath, hourBandPaint);
}
public class Event{
public int color;
public CharSequence title;
public boolean allDay;
public long startMillis;
public long endMillis;
public Event(String title,boolean allDay, long startMillis,long endMillis) {
this.title = title;
this.allDay = allDay;
this.startMillis = startMillis;
this.endMillis = endMillis;
}
}
}
环形textview
package com.hwj.android.learning;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import java.util.Locale;
public class ArcTextView extends View {
private int centerX, centerY;
private Paint rectPaint;
private Paint txtPaint;
private RectF txtRectF;
private Path txtPath;
private Paint circlePaint;
private float fontSize = 17.65f;
//根据图片一共24小时,则一个时钟供24段,顾每段的角度为15度,每段代表一个小时,顾每秒的角度为15/3600 = 0.00416666666666度
private final double DEGREE_FOR_ONESEC = 0.00416666666666; // 15 degree / 3600 sec
private double degreeForOneChar = 2.0f;
private final float CIRCLE_LIMIT = 359.9999f;
private final int alpha = 240; // org - 240
private float startAngle, endAngle, sweepAngle;
private float radiusIn, radiusOut;
private String firstText, secondText;
private Handler animationHandler;
private boolean isRectAnimationMode = true;
private boolean isCircleAnimationMode = true;
private boolean isFirstTimeDraw = true;
private int animationDuration = 300; // 600 msec
private float intervalAngle = 10f;
private float curSweepAngle = 0f;
private int animationUpdateInterval = 10; // 10 msec
private float intervalRadius = 0f;
private float maxRadius = 0f;
private float curRadius = 0f;
private final int EVENT_CIRCLE = 0;
private final int EVENT_RECT = 1;
private float circleCenterX, circleCenterY;
private AnimationEndListener listener;
public ArcTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initSetting();
}
public ArcTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initSetting();
}
public ArcTextView(Context context) {
super(context);
initSetting();
}
public void setAnimationEndListener(AnimationEndListener listener) {
this.listener = listener;
}
//根据当前事件的时间计算当前事件的绘制角度
private double convertTimeAngle(long timeSec) {
double degreeAngle = timeSec * DEGREE_FOR_ONESEC;
return degreeAngle;
}
public void setInitParameter(ArcTextViewData txtViewData) {
updateTextViewData(txtViewData);
}
public boolean isRectAnimationMode() {
return isRectAnimationMode;
}
public boolean isCircleAnimationMode() {
return isCircleAnimationMode;
}
private void updateTextViewData(ArcTextViewData txtViewData) {
String startTime, endTime;
long startTimeSec, endTimeSec;
startTimeSec = txtViewData.startTimeSec;
endTimeSec = txtViewData.endTimeSec;
DataConverter converter = new DataConverter();
startTime = converter.convertSecToString(startTimeSec);
endTime = converter.convertSecToString(endTimeSec);
String scheduleTitle = txtViewData.scheduleTitle;
firstText = startTime + " ~ " + endTime;
secondText = scheduleTitle;
startAngle = (float) convertTimeAngle(startTimeSec);
endAngle = (float) convertTimeAngle(endTimeSec);
//起点时间和终点时间之间的滑动角度
sweepAngle = endAngle - startAngle;
radiusIn = txtViewData.radiusIn;
radiusOut = txtViewData.radiusOut;
rectPaint.setColor(txtViewData.backgroundColor);
circlePaint.setColor(txtViewData.backgroundColor);
curSweepAngle = intervalAngle;
}
private void onHandleMessage(Message msg) {
switch (msg.what) {
case EVENT_CIRCLE: {
updateCircle();
break;
}
case EVENT_RECT: {
updateArc();
break;
}
}
}
//环形文字的中心位置
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
centerX = (right - left) / 2;
centerY = (bottom - top) / 2;
}
private void initSetting() {
centerY = centerX = 0;
startAngle = sweepAngle = 0f;
circleCenterX = circleCenterY = 0f;
rectPaint = new Paint();
rectPaint.setStyle(Paint.Style.FILL_AND_STROKE);
rectPaint.setAntiAlias(true);
rectPaint.setDither(true);
rectPaint.setColor(Color.BLUE);
rectPaint.setAlpha(alpha);
txtPaint = new Paint();
txtPaint.setTextSize(fontSize);
txtPaint.setTextAlign(Align.CENTER);
txtPaint.setColor(Color.BLACK);
txtRectF = new RectF();
txtPath = new Path();
circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.FILL);
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
circlePaint.setColor(Color.BLUE);
circlePaint.setAlpha(alpha);
animationHandler = new Handler() {
public void handleMessage(Message msg) {
onHandleMessage(msg);
}
};
}
private void drawArcSegment(Canvas canvas, float cx, float cy,
float rInn, float rOut, float startAngle, float sweepAngle,
Paint fill, Paint stroke) {
RectF outerRect = new RectF(cx - rOut, cy - rOut, cx + rOut, cy + rOut);
RectF innerRect = new RectF(cx - rInn, cy - rInn, cx + rInn, cy + rInn);
Path segmentPath = new Path();
double start = Math.toRadians(startAngle);
segmentPath.moveTo((float) (cx + rInn * Math.cos(start)), (float) (cy + rInn * Math.sin(start)));
segmentPath.lineTo((float) (cx + rOut * Math.cos(start)), (float) (cy + rOut * Math.sin(start)));
//画一个弧线的路径 https://blog.csdn.net/whyrjj3/article/details/7940385
segmentPath.arcTo(outerRect, startAngle, sweepAngle);
double end = Math.toRadians(startAngle + sweepAngle);
segmentPath.lineTo((float) (cx + rInn * Math.cos(end)), (float) (cy + rInn * Math.sin(end)));
segmentPath.arcTo(innerRect, startAngle + sweepAngle, -sweepAngle);
if (fill != null) {
canvas.drawPath(segmentPath, fill);
}
if (stroke != null) {
canvas.drawPath(segmentPath, stroke);
}
}
// one char per 3 degree
private String getEllipsizedText(String inText, float arcAngle) {
int length = inText.length();
String retString = "";
float reqAngle = (float) (length * degreeForOneChar);
if (arcAngle >= reqAngle)
retString = inText;
else {
int availTextLength = (int) (arcAngle / degreeForOneChar) - 2;
if (availTextLength < 0) {
retString = "";
}
else if (availTextLength == 0) {
retString = String.valueOf(inText.charAt(0));
}
else {
retString = inText.substring(0, availTextLength) + "...";
}
}
return retString;
}
private void initDrawCircle() {
double start = Math.toRadians(startAngle);
float startX = (float) (centerX + radiusIn * Math.cos(start));
float startY = (float) (centerY + radiusIn * Math.sin(start));
float endX = (float) (centerX + radiusOut * Math.cos(start));
float endY = (float) (centerY + radiusOut * Math.sin(start));
double distbwpoints = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
maxRadius = (float) (distbwpoints / 2);
intervalRadius = maxRadius / (animationDuration / animationUpdateInterval);
circleCenterX = (endX > startX) ? startX + (endX - startX) / 2 : startX - (startX - endX) / 2;
circleCenterY = (endY > startY) ? startY + (endY - startY) / 2 : startY - (startY - endY) / 2;
}
private void updateCircle() {
curRadius += intervalRadius;
if (curRadius < maxRadius) {
animationHandler.sendEmptyMessageDelayed(EVENT_CIRCLE, animationUpdateInterval);
invalidate();
} else {
isCircleAnimationMode = false;
if (listener != null) {
listener.onAnimationEnd(AnimationEndListener.TYPE_CIRCLE);
}
}
}
public void startRectAnimation() {
isRectAnimationMode = true;
animationHandler.sendEmptyMessageDelayed(EVENT_RECT, animationUpdateInterval);
invalidate();
}
private void updateArc() {
curSweepAngle += intervalAngle;
if (curSweepAngle < sweepAngle)
animationHandler.sendEmptyMessageDelayed(EVENT_RECT, animationUpdateInterval);
else {
isRectAnimationMode = false;
//弧形文字动画结束,需要不断更新时针的位置
if (listener != null) {
listener.onAnimationEnd(AnimationEndListener.TYPE_RECT);
}
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isFirstTimeDraw) {
isFirstTimeDraw = false;
initDrawCircle();
animationHandler.sendEmptyMessageDelayed(EVENT_CIRCLE, animationUpdateInterval);
}
if (isCircleAnimationMode) {
canvas.drawCircle(circleCenterX, circleCenterY, curRadius, circlePaint);
return;
}
if (isRectAnimationMode) {
drawArcSegment(canvas, centerX, centerY, radiusIn, radiusOut, startAngle, curSweepAngle, rectPaint, null);
} else {
if (sweepAngle < 360) {
drawArcSegment(canvas, centerX, centerY, radiusIn, radiusOut, startAngle, sweepAngle, rectPaint, null);
} else {
// for circle, draw arc 2 times,这样才能形成一个整圆
for (int i = 0; i < 2; i++) {
drawArcSegment(canvas, centerX, centerY, radiusIn, radiusOut, 180 * i, 180, rectPaint, null);
}
}
}
// Draw Text in the middle of Arc
float r = (radiusIn + radiusOut) / 2;
txtRectF.left = centerX - r;
txtRectF.top = centerY - r;
txtRectF.right = centerX + r;
txtRectF.bottom = centerY + r;
txtPath.reset();
if (isRectAnimationMode) {
if (startAngle >= 0 && startAngle <= 180) {
txtPath.addArc(txtRectF, startAngle + curSweepAngle, -curSweepAngle);
}
else {
txtPath.addArc(txtRectF, startAngle, curSweepAngle);
}
canvas.drawTextOnPath(getEllipsizedText(firstText, curSweepAngle), txtPath, 3, 0, txtPaint);
canvas.drawTextOnPath(getEllipsizedText(secondText, curSweepAngle), txtPath, 3, 20, txtPaint);
} else {
if (startAngle >= 0 && startAngle <= 180) {
txtPath.addArc(txtRectF, endAngle, startAngle - endAngle);
}
else{
txtPath.addArc(txtRectF, startAngle, sweepAngle);
}
canvas.drawTextOnPath(getEllipsizedText(firstText, sweepAngle), txtPath, 3, 0, txtPaint);
canvas.drawTextOnPath(getEllipsizedText(secondText, sweepAngle), txtPath, 3, 20, txtPaint);
}
}
public class DataConverter {
public String convertSecToString(long timeSec) {
long hourMs = 3600;
long minMS = 60;
long hours = timeSec / hourMs;
long mins = (timeSec - hours*hourMs) / minMS;
String timeString = String.format(Locale.getDefault(), "%02d:%02d", hours, mins);
return timeString;
}
}
}
一些工具类
package com.hwj.android.learning;
public interface AnimationEndListener {
public static final int TYPE_CIRCLE = 0;
public static final int TYPE_RECT = 1;
public abstract void onAnimationEnd(int type);
}
package com.hwj.android.learning;
import android.graphics.Color;
public class ArcTextViewData {
public int backgroundColor;
public float radiusIn;
public float radiusOut;
public long startTimeSec;
public long endTimeSec;
public String scheduleTitle;
public ArcTextViewData() {
backgroundColor = Color.BLACK;
radiusIn = radiusOut = 0.0f;
startTimeSec = endTimeSec = 0;
}
}
特效文件 bottom_to_top_slide_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%"
android:toYDelta="0%"
android:duration="400"
android:fillAfter="true"/>
</set>
特效文件 scale_0_to_1.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<scale android:fromXScale="0.0" android:fromYScale="0.0"
android:toXScale="1.0" android:toYScale="1.0"
android:pivotX="50%" android:pivotY="50%"
android:duration="2000" android:fillBefore="false" />
</set>
//arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="month_type_array">
<item>january</item>
<item>february</item>
<item>march</item>
<item>april</item>
<item>may</item>
<item>june</item>
<item>july</item>
<item>august</item>
<item>september</item>
<item>october</item>
<item>november</item>
<item>december</item>
</string-array>
<string-array name="day_of_week_type_array">
<item>sunday</item>
<item>monday</item>
<item>tuesday</item>
<item>wednesday</item>
<item>thursday</item>
<item>friday</item>
<item>saturday</item>
</string-array>
</resources>