前言:午休难得有时间闲下来看看新闻,打开uc,打开了一张美女图片,然后看到这么一个画面
这个loaddingview深深吸引着我,此时我很不愿意让这张图片加载出来了(loaddingview的魅力已经大于美女的魅力了),尼玛!!这就是程序猿的世界么??哈哈~~~
好啦!不扯了,进入正题,今天我们要实现的就是这么一个loaddingview,先来一张最终的效果:
显示图片:
实现方式:
1、用贝塞尔曲线画一个四角是round的正方形,然后按进度画出进度条path。
2、用pathmeasure类根据当前的进度截取path,然后画出path。
小伙伴也不要思维定式了,也有很多种实现方式(关键是有想法一定要实践,嘻嘻~)今天我们主要讲pathmeasure类这种方式。
先介绍下PathMeasure这个类(顾名思义,PathMeasure是一个用来测量Path的类)主要有以下方法::
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics;
public class PathMeasure {
private Path mPath;
/**
* Create an empty PathMeasure object. To uses this to measure the length
* of a path, and/or to find the position and tangent along it, call
* setPath.
*
* Note that once a path is associated with the measure object, it is
* undefined if the path is subsequently modified and the the measure object
* is used. If the path is modified, you must call setPath with the path.
*/
public PathMeasure() {
mPath = null;
native_instance = native_create(0, false);
}
/**
* Create a PathMeasure object associated with the specified path object
* (already created and specified). The measure object can now return the
* path's length, and the position and tangent of any position along the
* path.
*
* Note that once a path is associated with the measure object, it is
* undefined if the path is subsequently modified and the the measure object
* is used. If the path is modified, you must call setPath with the path.
*
* @param path The path that will be measured by this object
* @param forceClosed If true, then the path will be considered as "closed"
* even if its contour was not explicitly closed.
*/
public PathMeasure(Path path, boolean forceClosed) {
// The native implementation does not copy the path, prevent it from being GC'd
mPath = path;
native_instance = native_create(path != null ? path.ni() : 0,
forceClosed);
}
/**
* Assign a new path, or null to have none.
*/
public void setPath(Path path, boolean forceClosed) {
mPath = path;
native_setPath(native_instance,
path != null ? path.ni() : 0,
forceClosed);
}
/**
* Return the total length of the current contour, or 0 if no path is
* associated with this measure object.
*/
public float getLength() {
return native_getLength(native_instance);
}
/**
* Pins distance to 0 <= distance <= getLength(), and then computes the
* corresponding position and tangent. Returns false if there is no path,
* or a zero-length path was specified, in which case position and tangent
* are unchanged.
*
* @param distance The distance along the current contour to sample
* @param pos If not null, eturns the sampled position (x==[0], y==[1])
* @param tan If not null, returns the sampled tangent (x==[0], y==[1])
* @return false if there was no path associated with this measure object
*/
public boolean getPosTan(float distance, float pos[], float tan[]) {
if (pos != null && pos.length < 2 ||
tan != null && tan.length < 2) {
throw new ArrayIndexOutOfBoundsException();
}
return native_getPosTan(native_instance, distance, pos, tan);
}
public static final int POSITION_MATRIX_FLAG = 0x01; // must match flags in SkPathMeasure.h
public static final int TANGENT_MATRIX_FLAG = 0x02; // must match flags in SkPathMeasure.h
/**
* Pins distance to 0 <= distance <= getLength(), and then computes the
* corresponding matrix. Returns false if there is no path, or a zero-length
* path was specified, in which case matrix is unchanged.
*
* @param distance The distance along the associated path
* @param matrix Allocated by the caller, this is set to the transformation
* associated with the position and tangent at the specified distance
* @param flags Specified what aspects should be returned in the matrix.
*/
public boolean getMatrix(float distance, Matrix matrix, int flags) {
return native_getMatrix(native_instance, distance, matrix.native_instance, flags);
}
/**
* Given a start and stop distance, return in dst the intervening
* segment(s). If the segment is zero-length, return false, else return
* true. startD and stopD are pinned to legal values (0..getLength()).
* If startD <= stopD then return false (and leave dst untouched).
* Begin the segment with a moveTo if startWithMoveTo is true.
*
* <p>On {@link android.os.Build.VERSION_CODES#KITKAT} and earlier
* releases, the resulting path may not display on a hardware-accelerated
* Canvas. A simple workaround is to add a single operation to this path,
* such as <code>dst.rLineTo(0, 0)</code>.</p>
*/
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
dst.isSimplePath = false;
return native_getSegment(native_instance, startD, stopD, dst.ni(), startWithMoveTo);
}
/**
* Return true if the current contour is closed()
*/
public boolean isClosed() {
return native_isClosed(native_instance);
}
/**
* Move to the next contour in the path. Return true if one exists, or
* false if we're done with the path.
*/
public boolean nextContour() {
return native_nextContour(native_instance);
}
protected void finalize() throws Throwable {
native_destroy(native_instance);
}
private static native long native_create(long native_path, boolean forceClosed);
private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);
private static native float native_getLength(long native_instance);
private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);
private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);
private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);
private static native boolean native_isClosed(long native_instance);
private static native boolean native_nextContour(long native_instance);
private static native void native_destroy(long native_instance);
/* package */private final long native_instance;
}
实现原理都是一些native的方法,主要java方法:
| 方法名 | 说明 |
|---|---|
| PathMeasure () | 构造方法 |
| PathMeasure(Path path, boolean forceClosed) | 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。 |
| setPath(Path path, boolean forceClosed) | path:关联传入的path |
| isClosed() | 是否闭合 |
| getLength() | 获取path的长度 |
| nextContour() | 跳转到下一个轮廓 |
| getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 截取相应的path |
| getPosTan(float distance, float[] pos, float[] tan) | 根据传入的distance获取当前path的位置跟tan值 |
| getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点Matrix |
方法使用起来都很简单,我们这次的demo中需要用到getSegment根据当前进度截取path,然后显示出来(没错!就是这么简单)
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
dst.isSimplePath = false;
return native_getSegment(native_instance, startD, stopD, dst.ni(), startWithMoveTo);
}
我们需要截取一段path需要传入参数:截取的起始点(startD)、截取的结束点(stopD)、目标path,startWithMoveTo起始点是否使用 moveTo 用于保证截取的 Path 第一个点位置不变。
代码比较简单,都有注释,我就不一一解释了:
package com.leo.camerroll.camera;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
/**
* Created by leo on 17/2/10.
*/
public class LoaddingView extends View {
/**
* 左眼距离左边的距离(控件宽度*EYE_PERCENT_W),
* 右眼距离右边的距离(控件宽度*EYE_PERCENT_W)
*/
private static final float EYE_PERCENT_W = 0.35F;
/**
*眼睛距离top的距离(控件的高度*EYE_PERCENT_H)
*/
private static final float EYE_PERCENT_H = 0.38F;
/**
* 嘴巴左边跟右边距离top的距离(控件的高度*MOUCH_PERCENT_H)
*/
private static final float MOUCH_PERCENT_H = 0.55F;
/**
* 嘴巴中间距离top的距离(控件的高度*MOUCH_PERCENT_H2)
*/
private static final float MOUCH_PERCENT_H2 = 0.7F;
/**
* 嘴巴左边跟右边距离边缘的位置(控件宽度*MOUCH_PERCENT_W)
*/
private static final float MOUCH_PERCENT_W = 0.23F;
/**
* 眼睛跟嘴巴摆动的区间范围
*/
private static final float DURATION_AREA = 0.15F;
/**
* 眼睛跟嘴巴摆动的动画
*/
Animation a=new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float offset=interpolatedTime*DURATION_AREA;
mMouchH=MOUCH_PERCENT_H+offset;
mMouchH2=MOUCH_PERCENT_H2+offset;
mEyesH=EYE_PERCENT_H+offset;
postInvalidate();
}
};
private Paint reachedPaint;
private Paint unreachedPaint;
private Path reachedPath;
private Path unreachedPath;
private Path mouthPath=new Path();
private float mProgress=0.1f;
private float lineWidth=dp2px(2);
private float mRadius;
private float mMouchH=MOUCH_PERCENT_H;
private float mMouchH2=MOUCH_PERCENT_H2;
private float mEyesH=EYE_PERCENT_H;
public LoaddingView(Context context) {
this(context, null);
}
public LoaddingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoaddingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void startAni() {
a.setDuration(500);
a.setRepeatCount(Animation.INFINITE);
a.setRepeatMode(Animation.REVERSE);
startAnimation(a);
}
private void initView() {
reachedPaint=new Paint(Paint.ANTI_ALIAS_FLAG| Paint.DITHER_FLAG);
reachedPaint.setStyle(Paint.Style.STROKE);
reachedPaint.setStrokeWidth(lineWidth);
reachedPaint.setColor(Color.WHITE);
reachedPaint.setStrokeJoin(Paint.Join.ROUND);
reachedPaint.setStrokeCap(Paint.Cap.ROUND);
unreachedPaint=new Paint(reachedPaint);
unreachedPaint.setColor(Color.GRAY);
}
private boolean isStart=true;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(isStart){
startAni();
isStart=false;
}
mRadius=getWidth()/7F/2;
if(unreachedPath==null){
unreachedPath=new Path();
}
unreachedPath.addRoundRect(new RectF(lineWidth,lineWidth,w-lineWidth,h-lineWidth),w/6,w/6, Path.Direction.CCW);
if(reachedPath==null){
reachedPath=new Path();
}
reachedPath.addRoundRect(new RectF(lineWidth,lineWidth,w-lineWidth,h-lineWidth),w/6,w/6, Path.Direction.CW);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
canvas.save();
//draw face
drawFace(canvas);
//drawreached rect
drawReachedRect(canvas);
canvas.restore();
}
/**
* draw face
*/
private void drawFace(Canvas canvas) {
unreachedPaint.setStyle(Paint.Style.FILL);
//画左边的眼睛
canvas.drawCircle(getWidth()*EYE_PERCENT_W,getHeight()*mEyesH-mRadius,mRadius,unreachedPaint);
//画右边的眼睛
canvas.drawCircle(getWidth()*(1-EYE_PERCENT_W),getHeight()*mEyesH-mRadius,mRadius,unreachedPaint);
mouthPath.reset();
//画嘴巴
mouthPath.moveTo(getWidth()*MOUCH_PERCENT_W,getHeight()*mMouchH);
mouthPath.quadTo(getWidth()/2,getHeight()*mMouchH2,getWidth()*(1-MOUCH_PERCENT_W),getHeight()*mMouchH);
unreachedPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mouthPath,unreachedPaint);
}
private void drawReachedRect(Canvas canvas) {
unreachedPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(unreachedPath,unreachedPaint);
PathMeasure measure=new PathMeasure(reachedPath,false);
float length = measure.getLength();
//获取当前path长度
float currLength=length*mProgress;
Path path=new Path();
/**
* 因为uc的起始位置是在顶部的位置,而我们的path的起始位置是在左下的位置,
* 所以我们需要加上一个length*1/3f偏移量
*/
measure.getSegment(length*1/3f,currLength+length*1/3f,path,true);
canvas.drawPath(path,reachedPaint);
/**
* 当mProgress>=2/3f的时候,也就是回到了起点的时候,我们得截取两段path了
* 一段是1/3的位置到2/3
* 一段是0到1/3的位置
*/
if(mProgress>=2/3f){
Path path2=new Path();
measure.getSegment(0,length*(mProgress-2/3f),path2,true);
canvas.drawPath(path2,reachedPaint);
}
}
public float dp2px(float dpValue){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}
public void setProgress(float progress){
Log.e("TAG",""+progress);
if(progress<mProgress){
return;
}
this.mProgress=progress;
if(Looper.myLooper()==Looper.getMainLooper()){
invalidate();
}else{
postInvalidate();
}
}
public void loadComplete() {
a.cancel();
clearAnimation();
setVisibility(View.GONE);
}
}
最后附上github链接:
https://github.com/913453448/CamerRoll
加载视图绘制
本文介绍了一个自定义加载视图的实现方法,通过PathMeasure类截取路径的方式展示加载进度,同时实现了视图中眼睛和嘴巴的动态效果。
5920

被折叠的 条评论
为什么被折叠?



