整体效果
思路
1、该流向图需要实现四个位置,顺时针逆时针都可以流动所以需要做八种判断(左上顺时针,左上逆时针,右上顺时针等)
2、该View的颜色要可配置
3、流动速度要可配置
考虑用Path类先定义出底部的路径,Paint类实现颜色的替换。根据Path播放属性动画,在动画播放过程中,用画笔实时更新点的位置实现流动效果
1、首先创建相应属性,在styleable.xml中创建
< declare- styleable name= "Direction" >
< attr name= "line_color" format= "reference" / >
< attr name= "direction" >
< enum name= "left_top" value= "0" / >
< enum name= "left_bottom" value= "1" / >
< enum name= "right_top" value= "2" / >
< enum name= "right_bottom" value= "3" / >
< / attr>
< attr name= "animation_time" format= "integer" / >
< attr name= "flow" >
< enum name= "pass" value= "0" / >
< enum name= "inverse" value= "1" / >
< / attr>
< / declare- styleable>
2、自定义一个view,在构造函数中获取到自定义的属性值
public class DirectionView extends View {
public final static Integer LEFT_TOP = 0 ;
public final static Integer LEFT_BOTTOM = 1 ;
public final static Integer RIGHT_TOP = 2 ;
public final static Integer RIGHT_BOTTOM = 3 ;
public final static Integer PASS = 0 ;
public final static Integer INVERSE = 1 ;
private int mTotalWidth, mTotalHeight;
private Paint linePaint;
private Paint pointPaint;
private float cirle = 60 ;
private float [ ] mCurrentPosition = new float [ 2 ] ;
private int padding = 10 ;
private int color;
private int direction;
private int animationTime;
private int flowDirection;
public DirectionView ( Context context, AttributeSet attrs, int defStyleAttr) {
super ( context, attrs, defStyleAttr) ;
}
public DirectionView ( Context context, AttributeSet attrs) {
super ( context, attrs) ;
TypedArray ta = context. obtainStyledAttributes ( attrs, R. styleable. Direction) ;
color = ta. getColor ( R. styleable. Direction_line_color, Color. GREEN) ;
direction = ta. getInt ( R. styleable. Direction_direction, 0 ) ;
animationTime = ta. getInt ( R. styleable. Direction_animation_time, 5 ) ;
flowDirection = ta. getInt ( R. styleable. Direction_flow, 0 ) ;
initPaint ( ) ;
}
private void initPaint ( ) {
linePaint = new Paint ( ) ;
linePaint. setAntiAlias ( true ) ;
linePaint. setDither ( true ) ;
linePaint. setFilterBitmap ( true ) ;
linePaint. setColor ( color) ;
linePaint. setStyle ( Paint. Style. STROKE) ;
linePaint. setStrokeWidth ( 5 ) ;
pointPaint = new Paint ( ) ;
pointPaint. setAntiAlias ( true ) ;
pointPaint. setDither ( true ) ;
pointPaint. setFilterBitmap ( true ) ;
pointPaint. setColor ( color) ;
pointPaint. setStyle ( Paint. Style. STROKE) ;
pointPaint. setStrokeWidth ( 10 ) ;
}
private PathMeasure pathMeasure;
private Path linePath;
private void initPath ( ) {
linePath = new Path ( ) ;
if ( direction == LEFT_TOP) {
if ( flowDirection == INVERSE) {
linePath. moveTo ( padding, mTotalHeight) ;
linePath. lineTo ( mTotalWidth - cirle, mTotalHeight) ;
RectF oval = new RectF ( mTotalWidth - 2 * cirle, mTotalHeight - 2 * cirle, mTotalWidth, mTotalHeight) ;
linePath. arcTo ( oval, 90 , - 90 ) ;
linePath. lineTo ( mTotalWidth, padding) ;
} else if ( flowDirection == PASS) {
linePath. moveTo ( mTotalWidth, padding) ;
linePath. lineTo ( mTotalWidth, mTotalHeight - cirle) ;
RectF oval = new RectF ( mTotalWidth - 2 * cirle, mTotalHeight - 2 * cirle, mTotalWidth, mTotalHeight) ;
linePath. arcTo ( oval, 0 , 90 ) ;
linePath. lineTo ( padding, mTotalHeight) ;
}
} else if ( direction == LEFT_BOTTOM) {
if ( flowDirection == INVERSE) {
linePath. moveTo ( mTotalWidth, mTotalHeight) ;
linePath. lineTo ( mTotalWidth, cirle + padding) ;
RectF oval = new RectF ( mTotalWidth - 2 * cirle, padding, mTotalWidth, padding + 2 * cirle) ;
linePath. arcTo ( oval, 0 , - 90 ) ;
linePath. lineTo ( padding, padding) ;
} else if ( flowDirection == PASS) {
linePath. moveTo ( padding, padding) ;
linePath. lineTo ( mTotalWidth - cirle, padding) ;
RectF oval = new RectF ( mTotalWidth - 2 * cirle, padding, mTotalWidth, padding + 2 * cirle) ;
linePath. arcTo ( oval, 270 , 90 ) ;
linePath. lineTo ( mTotalWidth, mTotalHeight) ;
}
} else if ( direction == RIGHT_TOP) {
if ( flowDirection == INVERSE) {
linePath. moveTo ( padding, padding) ;
linePath. lineTo ( padding, mTotalHeight - cirle) ;
RectF oval = new RectF ( padding, mTotalHeight - 2 * cirle, padding + 2 * cirle, mTotalHeight) ;
linePath. arcTo ( oval, 180 , - 90 ) ;
linePath. lineTo ( mTotalWidth, mTotalHeight) ;
} else if ( flowDirection == PASS) {
linePath. moveTo ( mTotalWidth, mTotalHeight) ;
linePath. lineTo ( padding + cirle, mTotalHeight) ;
RectF oval = new RectF ( padding, mTotalHeight - 2 * cirle, padding + 2 * cirle, mTotalHeight) ;
linePath. arcTo ( oval, 90 , 90 ) ;
linePath. lineTo ( padding, padding) ;
}
} else if ( direction == RIGHT_BOTTOM) {
if ( flowDirection == INVERSE) {
linePath. moveTo ( mTotalWidth, padding) ;
linePath. lineTo ( padding + cirle, padding) ;
RectF oval = new RectF ( padding, padding, padding + 2 * cirle, padding + 2 * cirle) ;
linePath. arcTo ( oval, 270 , - 90 ) ;
linePath. lineTo ( padding, mTotalHeight) ;
} else if ( flowDirection == PASS) {
linePath. moveTo ( padding, mTotalHeight) ;
linePath. lineTo ( padding, padding + cirle) ;
RectF oval = new RectF ( padding, padding, padding + 2 * cirle, padding + 2 * cirle) ;
linePath. arcTo ( oval, 180 , 90 ) ;
linePath. lineTo ( mTotalWidth, padding) ;
}
}
pathMeasure = new PathMeasure ( linePath, false ) ;
ValueAnimator valueAnimator = ValueAnimator. ofFloat ( 0 , pathMeasure. getLength ( ) ) ;
valueAnimator. setDuration ( animationTime * 1000 ) ;
valueAnimator. setInterpolator ( new LinearInterpolator ( ) ) ;
valueAnimator. setRepeatCount ( ValueAnimator. INFINITE) ;
valueAnimator. addUpdateListener ( new ValueAnimator. AnimatorUpdateListener ( ) {
@Override
public void onAnimationUpdate ( ValueAnimator animation) {
float value = ( float ) animation. getAnimatedValue ( ) ;
pathMeasure. getPosTan ( value, mCurrentPosition, null) ;
Log. i ( "zyq" , mCurrentPosition[ 0 ] + " " + mCurrentPosition[ 1 ] ) ;
invalidate ( ) ;
}
} ) ;
valueAnimator. start ( ) ;
}
@Override
protected void onDraw ( final Canvas canvas) {
super . onDraw ( canvas) ;
canvas. drawPath ( linePath, linePaint) ;
canvas. drawCircle ( mCurrentPosition[ 0 ] , mCurrentPosition[ 1 ] , 5 , pointPaint) ;
}
@Override
protected void onSizeChanged ( int w, int h, int oldw, int oldh) {
super . onSizeChanged ( w, h, oldw, oldh) ;
mTotalWidth = w - padding;
mTotalHeight = h - padding;
initPath ( ) ;
}
}
3、在xml中使用并更换属性
< LinearLayout
android: layout_marginTop= "@dimen/space_300"
android: layout_width= "match_parent"
android: layout_height= "@dimen/space_200"
android: orientation= "vertical" >
< LinearLayout
android: layout_width= "match_parent"
android: layout_height= "0dp"
android: layout_weight= "1"
android: orientation= "horizontal" >
< com. aiswei. storage. customview. DirectionView
android: layout_width= "0dp"
android: layout_height= "match_parent"
android: layout_gravity= "center_horizontal"
android: layout_marginTop= "@dimen/space_20"
android: layout_weight= "1"
android: padding= "@dimen/space_5"
app: direction= "left_top"
app: flow= "inverse" / >
< com. aiswei. storage. customview. DirectionView
android: layout_width= "0dp"
android: layout_height= "match_parent"
android: layout_gravity= "center_horizontal"
android: layout_marginTop= "@dimen/space_20"
android: layout_weight= "1"
android: padding= "@dimen/space_5"
app: direction= "right_top"
app: flow= "pass" / >
< / LinearLayout>
< LinearLayout
android: layout_width= "match_parent"
android: layout_height= "0dp"
android: layout_weight= "1"
android: orientation= "horizontal" >
< com. aiswei. storage. customview. DirectionView
android: layout_width= "0dp"
android: layout_height= "match_parent"
android: layout_gravity= "center_horizontal"
android: layout_marginTop= "@dimen/space_20"
android: layout_weight= "1"
android: padding= "@dimen/space_5"
app: direction= "left_bottom"
app: flow= "pass" / >
< com. aiswei. storage. customview. DirectionView
android: layout_width= "0dp"
android: layout_height= "match_parent"
android: layout_gravity= "center_horizontal"
android: layout_marginTop= "@dimen/space_20"
android: layout_weight= "1"
android: padding= "@dimen/space_5"
app: direction= "right_bottom"
app: flow= "inverse" / >
< / LinearLayout>
< / LinearLayout>
欢迎指教和询问