版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/devallever/article/details/78356124
寫在前面
這是一個顯示用戶當前排位的進度條,使用貝塞爾曲線,上面是用戶排位,當我運動時,會前進,走過的部分顏色變藍色,未走過的部分未灰色。當用戶走到曲線盡頭時,會刷新,數據點後移,這樣可以永遠向前移動。效果如下:
黃色點未當前用戶,可實時移動。左上角爲運動距離,模擬用戶移動。當黃色點移動到最右,所有點在曲線上向後移動。
詳細實現
初始化貝塞爾曲線數據
private void initPointList(){
mPointFList.clear();
mPointFList.add(new PointF(mMarginLeftRight,mHeight/2));
mPointFList.add(new PointF(mWidth- mMarginLeftRight,mHeight/2));
mBezierLineDataList = getLineData(mPointFList);
}
getLineData();
/**獲取每一段曲線所需要的點集*/
private List<BezierLineData> getLineData(List<PointF> pointList){
float t = 0.5f;
List<BezierLineData> lineDataList = new ArrayList<>();
PointF startP;
PointF endP;
PointF cp1;
PointF cp2;
BezierLineData lineData;
for (int i = 0; i<pointList.size() - 1;i ++){
startP = pointList.get(i);
endP = pointList.get(i+1);
cp1 = new PointF();
cp1.x = startP.x + (endP.x-startP.x) * t;
cp1.y = mMarginTopBottom - DensityUtil.dip2px(mContext,30f);
cp2 = new PointF();
cp2.x = startP.x + (endP.x-startP.x) * (1 - t);
cp2.y = mHeight - mMarginTopBottom + DensityUtil.dip2px(mContext,30f);
lineData = new BezierLineData(startP,endP,cp1,cp2);
lineDataList.add(lineData);
}
return lineDataList;
}
繪製貝塞爾曲線
private void drawBezier(Canvas canvas){
mLinePaint.setColor(mContext.getResources().getColor(R.color.competition_line_alpha_gray));
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(DensityUtil.dip2px(mContext,1.5f));//設置線寬
mLinePaint.setAntiAlias(true);//去除鋸齒
mLinePaint.setStrokeJoin(Paint.Join.ROUND);
mLinePaint.setStrokeCap(Paint.Cap.ROUND);
//繪製曲線
//Path lightLinePath = new Path();
mBezierPath.reset();
mBezierPath.moveTo(mBezierLineDataList.get(0).getStartP().x,mBezierLineDataList.get(0).getStartP().y);
for (int i=0;i<mBezierLineDataList.size();i++){
mBezierPath.cubicTo(
mBezierLineDataList.get(i).getCp1().x,mBezierLineDataList.get(i).getCp1().y,
mBezierLineDataList.get(i).getCp2().x,mBezierLineDataList.get(i).getCp2().y,
mBezierLineDataList.get(i).getEndP().x, mBezierLineDataList.get(i).getEndP().y
);
}
float gradientFactor = 0f;//比例,根據用戶當前位置設置漸變色的起始,用戶左邊爲藍色,右邊爲灰色
//求用戶所在位置佔總距離的比例
for (CompetitionData data: mCompetitionDataList){
if (data.getCompetitionUserType()== CompetitionUserType.ME) {
gradientFactor = data.getDistance()/mMaxDistance;
break;
}
}
LinearGradient linearGradient = new LinearGradient(
0,
0,
mWidth*gradientFactor,
0,
mGradientColor,
null,
Shader.TileMode.CLAMP
);
mLinePaint.setShader(linearGradient);
canvas.drawPath(mBezierPath,mLinePaint);
}
通過外部設置一些排行數據
/**
* 設置曲線數據
* @param competitionDataList 競賽數據列表
* 用戶數據排在第一*/
public void setLineData(List<CompetitionData> competitionDataList){
//獲取最大值
mCompetitionDataList = competitionDataList;
if (mCompetitionDataList.size() <= 4){
mCurDataIndex = mCompetitionDataList.size()-1;
}else {
mCurDataIndex = 4;
}
mMaxDistance = mCompetitionDataList.get(mCurDataIndex).getDistance();
mMaxDistance = mMaxDistance + mMaxDistance*0.1f;
getShowedDataList();//顯示4個數據
postInvalidateDelayed(50);
}
CompetitionData類
/**
* Created by allever on 17-9-20.
*/
public class CompetitionData {
private float distance;//運動距離
private CompetitionLine.CompetitionUserType competitionUserType;//用戶類型:ME當前用戶, OTHER:其他用戶
private int rank;//排名
public CompetitionData(CompetitionLine.CompetitionUserType competitionUserType , float distance, int rank){
this.rank = rank;
this.competitionUserType = competitionUserType;
this.distance = distance;
}
public float getDistance() {
return distance;
}
public void setDistance(float distance) {
this.distance = distance;
}
public CompetitionLine.CompetitionUserType getCompetitionUserType() {
return competitionUserType;
}
public void setCompetitionUserType(CompetitionLine.CompetitionUserType competitionUserType) {
this.competitionUserType = competitionUserType;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
}
每次只顯示5個記錄(我+其他比我大的最接近的四個用戶)
/**
* 只獲取顯示四個數據*/
private void getShowedDataList(){
mShowedDataList.clear();
mShowedDataList.add(mCompetitionDataList.get(0));//第一個,用戶
int fromIndex = 1;
Log.d(TAG, "getShowedDataList: mCurDataIndex = " + mCurDataIndex);
int counter = 0;
for (int i=mCurDataIndex;i>0;i--){
if (counter < 4){
fromIndex = i;
counter ++;
}
}
Log.d(TAG, "getShowedDataList: fromIndex = " + fromIndex);
for (int j=fromIndex;j<=mCurDataIndex;j++){
if (j < mCompetitionDataList.size()){
mShowedDataList.add(mCompetitionDataList.get(j));
Log.d(TAG, "getShowedDataList: j = " + j);
}
}
}
繪製圓點
private void drawMark(Canvas canvas){
if (mCompetitionDataList.size() == 0) return;
//繪製圓形與排行
Bitmap bitmap;
CompetitionData competitionData;
Log.d(TAG, "onDraw: mMaxDistance = " + mMaxDistance);
float t;//點在曲線上的比例
BezierLineData lineData;
PointF linePoint;
for (int i= 0; i<mShowedDataList.size(); i++){
competitionData = mShowedDataList.get(i);
lineData = mBezierLineDataList.get(0);//
t = competitionData.getDistance()/mMaxDistance;
//或取曲線上的點座標
linePoint = BezierUtil.calculateBezierPointForCubic(
t,
lineData.getStartP(),
lineData.getCp1(),
lineData.getCp2(),
lineData.getEndP());
if (competitionData.getCompetitionUserType() == CompetitionUserType.ME){
//是當前用戶,繪製Bitmap
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.run_video_piont);
canvas.drawBitmap(bitmap,
linePoint.x - (bitmap.getWidth()/2),
linePoint.y -(bitmap.getHeight()/2),
mImagePaint);
}else {
//其他用戶繪製圓點和排名
float r = DensityUtil.dip2px(mContext,10f);
mLinePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(
linePoint.x,
linePoint.y,
r,mLinePaint);
//繪製排名
String rank = competitionData.getRank()+"";
float dx;
float dy = DensityUtil.dip2px(mContext,4f);
if (competitionData.getRank() <10){
dx = DensityUtil.dip2px(mContext,3f);
}else if (competitionData.getRank() >= 10 && competitionData.getRank() < 100){
dx = DensityUtil.dip2px(mContext,6f);
}else {
dx = DensityUtil.dip2px(mContext,8f);
}
canvas.drawText(rank,0,rank.length(),
linePoint.x - dx,
linePoint.y + dy,
mRankPaint);
}
}
}
更新用戶進度
通過外部方法更新用戶運動距離,當用戶距離小於該曲線上顯示用戶的最大距離,僅觸發重繪,否則觸發滾動。
/**
* 更新運動進度
* @param distance 用戶當前運動距離*/
public void updateMyPoint(float distance){
CompetitionData myData = null;
for (int i=0; i<mCompetitionDataList.size();i++){
myData = mCompetitionDataList.get(i);
if (myData.getCompetitionUserType() == CompetitionUserType.ME){
myData.setDistance(distance);
mCompetitionDataList.set(i,myData);
break;
}
}
//postInvalidateDelayed(50);
if (myData == null) return;
if (myData.getDistance() > mCompetitionDataList.get(mCurDataIndex).getDistance()){
//獲取下一目標者的運動距離
if (mCurDataIndex+1 < mCompetitionDataList.size()){
mNextDistance = mCompetitionDataList.get(mCurDataIndex+1).getDistance();
//觸發滾動
//mHandler.sendEmptyMessageDelayed(MESSAGE_MOVE,100);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL,100);
}else {
//此時,用戶超越當前列表所有數據
Log.d(TAG, "updateMyPoint: max = " + mMaxDistance+"\t" + "mNextMax = " + mNextDistance);
if (myData.getDistance() > mNextDistance){
mNextDistance = myData.getDistance() + mMaxDistance * 0.3F;//當用戶超越當前排行榜列表用戶,再運動一定距離後,向後滾動一定距離
//觸發滾動
//mHandler.sendEmptyMessageDelayed(MESSAGE_MOVE,100);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL,100);
}else {
postInvalidateDelayed(50);
}
}
}else {
postInvalidateDelayed(50);
}
}
public enum CompetitionUserType{
ME,OTHER
}
Handler,曲線上點數據不變,而總距離自增,從而造成數據點向後滾動。
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//super.handleMessage(msg);
switch (msg.what){
case MESSAGE_SCROLL:
if (mMaxDistance < mNextDistance){
mMaxDistance ++ ;
invalidate();
sendEmptyMessage(MESSAGE_SCROLL);
}else {
mMaxDistance = mNextDistance;
if (mCurDataIndex < mCompetitionDataList.size()-1){
mCurDataIndex++;
}
getShowedDataList();//顯示4個數據
invalidate();
}
break;
}
}
};
一些全局變量
private List<CompetitionData> mCompetitionDataList = new ArrayList<>();//全部數據
private List<CompetitionData> mShowedDataList = new ArrayList<>();//可以展示的數據,
private int mCurDataIndex = 0;//用來記錄當前最大距離
private float mNextDistance;//下一目標者運動距離
//private float mDx = 0;//曲線每次移動偏移量
private List<PointF> mPointFList = new ArrayList<>();
private List<BezierLineData> mBezierLineDataList = new ArrayList<>();