自定义view 特效-环形View,数学在特效里面的运用

这篇文章主要实现如下效果,这实现的过程中,大家可以发现使用了一些数学里面的计算公式,其实特效是和数学知识分不开的
这里写图片描述

先介绍下这个特效的组成部分,这个特效有上下半区组成;
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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值