@Override
public void onFailed() {
if(getView() != null){
getView().getDataFailed();
}
}
});
}
然后再
然后先去把自定义View做好。这里面会用到一些图标
链接:图标提取地址
提取码:57sb
先写好工具类,在app下的utils包新建
package com.llw.goodweather.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.util.DisplayMetrics;
import android.util.TypedValue;
/**
- 测量工具类
*/
public class DisplayUtil {
public static int dip2px(Context context, int dp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,context.getResources().getDisplayMetrics());
}
protected static int sp2px(Context context, int sp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,context.getResources().getDisplayMetrics());
}
public static int px2dp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public static Bitmap bitmapResize(Bitmap src, float pxX, float pxY){
//压缩图片
Matrix matrix = new Matrix();
matrix.postScale(pxX / src.getWidth(), pxY / src.getHeight());
Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
return ret;
}
public static int getScreenWidth(Context context){
DisplayMetrics dm = context.getResources().getDisplayMetrics();
return dm.widthPixels;
}
public static int getScreenHeight(Context context){
DisplayMetrics dm = context.getResources().getDisplayMetrics();
return dm.heightPixels;
}
}
IconUtils.java,里面的图标就是从上面的链接里面下载下来的
package com.llw.goodweather.utils;
import com.llw.goodweather.R;
/**
- 图标工具类
*/
public class IconUtils {
/**
- 获取白天深色天气图标
*/
public static int getDayIconDark(String weather) {
int imageId;
switch (weather) {
case “100”:
imageId = R.mipmap.icon_100d;
break;
case “101”:
imageId = R.mipmap.icon_101d;
break;
case “102”:
imageId = R.mipmap.icon_102d;
break;
case “103”:
imageId = R.mipmap.icon_103d;
break;
case “104”:
imageId = R.mipmap.icon_104d;
break;
case “200”:
imageId = R.mipmap.icon_200d;
break;
case “201”:
imageId = R.mipmap.icon_210d;
break;
case “202”:
imageId = R.mipmap.icon_202d;
break;
case “203”:
imageId = R.mipmap.icon_203d;
break;
case “204”:
imageId = R.mipmap.icon_204d;
break;
case “205”:
imageId = R.mipmap.icon_205d;
break;
case “206”:
imageId = R.mipmap.icon_206d;
break;
case “207”:
imageId = R.mipmap.icon_207d;
break;
case “208”:
imageId = R.mipmap.icon_208d;
break;
case “209”:
imageId = R.mipmap.icon_209d;
break;
case “210”:
imageId = R.mipmap.icon_210d;
break;
case “211”:
imageId = R.mipmap.icon_211d;
break;
case “212”:
imageId = R.mipmap.icon_212d;
break;
case “213”:
imageId = R.mipmap.icon_213d;
break;
case “300”:
imageId = R.mipmap.icon_300d;
break;
case “301”:
imageId = R.mipmap.icon_301d;
break;
case “302”:
imageId = R.mipmap.icon_302d;
break;
case “303”:
imageId = R.mipmap.icon_303d;
break;
case “304”:
imageId = R.mipmap.icon_304d;
break;
case “305”:
imageId = R.mipmap.icon_305d;
break;
case “306”:
imageId = R.mipmap.icon_306d;
break;
case “307”:
imageId = R.mipmap.icon_307d;
break;
case “308”:
imageId = R.mipmap.icon_308d;
break;
case “309”:
imageId = R.mipmap.icon_309d;
break;
case “310”:
imageId = R.mipmap.icon_310d;
break;
case “311”:
imageId = R.mipmap.icon_311d;
break;
case “312”:
imageId = R.mipmap.icon_312d;
break;
case “313”:
imageId = R.mipmap.icon_313d;
break;
case “314”:
imageId = R.mipmap.icon_314d;
break;
case “315”:
imageId = R.mipmap.icon_315d;
break;
case “316”:
imageId = R.mipmap.icon_316d;
break;
case “317”:
imageId = R.mipmap.icon_317d;
break;
case “318”:
imageId = R.mipmap.icon_318d;
break;
case “399”:
imageId = R.mipmap.icon_399d;
break;
case “400”:
imageId = R.mipmap.icon_400d;
break;
case “401”:
imageId = R.mipmap.icon_401d;
break;
case “402”:
imageId = R.mipmap.icon_402d;
break;
case “403”:
imageId = R.mipmap.icon_403d;
break;
case “404”:
imageId = R.mipmap.icon_404d;
break;
case “405”:
imageId = R.mipmap.icon_405d;
break;
case “406”:
imageId = R.mipmap.icon_406d;
break;
case “407”:
imageId = R.mipmap.icon_407d;
break;
case “408”:
imageId = R.mipmap.icon_408d;
break;
case “409”:
imageId = R.mipmap.icon_409d;
break;
case “410”:
imageId = R.mipmap.icon_410d;
break;
case “499”:
imageId = R.mipmap.icon_499d;
break;
case “500”:
imageId = R.mipmap.icon_500d;
break;
case “501”:
imageId = R.mipmap.icon_501d;
break;
case “502”:
imageId = R.mipmap.icon_502d;
break;
case “503”:
imageId = R.mipmap.icon_503d;
break;
case “504”:
imageId = R.mipmap.icon_504d;
break;
case “507”:
imageId = R.mipmap.icon_507d;
break;
case “508”:
imageId = R.mipmap.icon_508d;
break;
case “509”:
imageId = R.mipmap.icon_509d;
break;
case “510”:
imageId = R.mipmap.icon_510d;
break;
case “511”:
imageId = R.mipmap.icon_511d;
break;
case “512”:
imageId = R.mipmap.icon_512d;
break;
case “513”:
imageId = R.mipmap.icon_513d;
break;
case “514”:
imageId = R.mipmap.icon_514d;
break;
case “515”:
imageId = R.mipmap.icon_515d;
break;
case “900”:
imageId = R.mipmap.icon_900d;
break;
case “901”:
imageId = R.mipmap.icon_901d;
break;
case “999”:
imageId = R.mipmap.icon_999d;
break;
default:
imageId = R.mipmap.icon_100d;
break;
}
return imageId;
}
/**
- 获取晚上深色天气图标
*/
public static int getNightIconDark(String weather) {
int imageId;
switch (weather) {
case “100”:
imageId = R.mipmap.icon_100n;
break;
case “101”:
imageId = R.mipmap.icon_101n;
break;
case “102”:
imageId = R.mipmap.icon_102n;
break;
case “103”:
imageId = R.mipmap.icon_103n;
break;
case “104”:
imageId = R.mipmap.icon_104n;
break;
case “200”:
imageId = R.mipmap.icon_200n;
break;
case “201”:
imageId = R.mipmap.icon_210n;
break;
case “202”:
imageId = R.mipmap.icon_202n;
break;
case “203”:
imageId = R.mipmap.icon_203n;
break;
case “204”:
imageId = R.mipmap.icon_204n;
break;
case “205”:
imageId = R.mipmap.icon_205n;
break;
case “206”:
imageId = R.mipmap.icon_206n;
break;
case “207”:
imageId = R.mipmap.icon_207n;
break;
case “208”:
imageId = R.mipmap.icon_208n;
break;
case “209”:
imageId = R.mipmap.icon_209n;
break;
case “210”:
imageId = R.mipmap.icon_210n;
break;
case “211”:
imageId = R.mipmap.icon_211n;
break;
case “212”:
imageId = R.mipmap.icon_212n;
break;
case “213”:
imageId = R.mipmap.icon_213n;
break;
case “300”:
imageId = R.mipmap.icon_300n;
break;
case “301”:
imageId = R.mipmap.icon_301n;
break;
case “302”:
imageId = R.mipmap.icon_302n;
break;
case “303”:
imageId = R.mipmap.icon_303n;
break;
case “304”:
imageId = R.mipmap.icon_304n;
break;
case “305”:
imageId = R.mipmap.icon_305n;
break;
case “306”:
imageId = R.mipmap.icon_306n;
break;
case “307”:
imageId = R.mipmap.icon_307n;
break;
case “308”:
imageId = R.mipmap.icon_308n;
break;
case “309”:
imageId = R.mipmap.icon_309n;
break;
case “310”:
imageId = R.mipmap.icon_310n;
break;
case “311”:
imageId = R.mipmap.icon_311n;
break;
case “312”:
imageId = R.mipmap.icon_312n;
break;
case “313”:
imageId = R.mipmap.icon_313n;
break;
case “314”:
imageId = R.mipmap.icon_314n;
break;
case “315”:
imageId = R.mipmap.icon_315n;
break;
case “316”:
imageId = R.mipmap.icon_316n;
break;
case “317”:
imageId = R.mipmap.icon_317n;
break;
case “318”:
imageId = R.mipmap.icon_318n;
break;
case “399”:
imageId = R.mipmap.icon_399n;
break;
case “400”:
imageId = R.mipmap.icon_400n;
break;
case “401”:
imageId = R.mipmap.icon_401n;
break;
case “402”:
imageId = R.mipmap.icon_402n;
break;
case “403”:
imageId = R.mipmap.icon_403n;
break;
case “404”:
imageId = R.mipmap.icon_404n;
break;
case “405”:
imageId = R.mipmap.icon_405n;
break;
case “406”:
imageId = R.mipmap.icon_406n;
break;
case “407”:
imageId = R.mipmap.icon_407n;
break;
case “408”:
imageId = R.mipmap.icon_408n;
break;
case “409”:
imageId = R.mipmap.icon_409n;
break;
case “410”:
imageId = R.mipmap.icon_410n;
break;
case “499”:
imageId = R.mipmap.icon_499n;
break;
case “500”:
imageId = R.mipmap.icon_500n;
break;
case “501”:
imageId = R.mipmap.icon_501n;
break;
case “502”:
imageId = R.mipmap.icon_502n;
break;
case “503”:
imageId = R.mipmap.icon_503n;
break;
case “504”:
imageId = R.mipmap.icon_504n;
break;
case “507”:
imageId = R.mipmap.icon_507n;
break;
case “508”:
imageId = R.mipmap.icon_508n;
break;
case “509”:
imageId = R.mipmap.icon_509n;
break;
case “510”:
imageId = R.mipmap.icon_510n;
break;
case “511”:
imageId = R.mipmap.icon_511n;
break;
case “512”:
imageId = R.mipmap.icon_512n;
break;
case “513”:
imageId = R.mipmap.icon_513n;
break;
case “514”:
imageId = R.mipmap.icon_514n;
break;
case “515”:
imageId = R.mipmap.icon_515n;
break;
case “900”:
imageId = R.mipmap.icon_900n;
break;
case “901”:
imageId = R.mipmap.icon_901n;
break;
case “999”:
imageId = R.mipmap.icon_999n;
break;
default:
imageId = R.mipmap.icon_100n;
break;
}
return imageId;
}
}
在com.llw.goodweather中新建view包,然后再建一个horizonview
下面一个一个来说明
先从接口来看
ScrollWatched.java
package com.llw.goodweather.view.horizonview;
/**
- 定义滑动监听接口
*/
public interface ScrollWatched {
void addWatcher(ScrollWatcher watcher);
void removeWatcher(ScrollWatcher watcher);
void notifyWatcher(int x);
}
ScrollWatcher.java
package com.llw.goodweather.view.horizonview;
/**
- 更新滑动
*/
public interface ScrollWatcher {
void update(int scrollX);
}
HourlyForecastView.java
package com.llw.goodweather.view.horizonview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.HourlyResponse;
import com.llw.goodweather.utils.DisplayUtil;
import com.llw.goodweather.utils.IconUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
- 24小时天气预报自定义View
*/
public class HourlyForecastView extends View implements ScrollWatcher {
private Context mContext;
//折线
private Paint foldLinePaint;
private Paint backPaint;
//底线
private Paint baseLinePaint;
//虚线
private Paint dashPaint;
//文字
private Paint textPaint;
//图片
private Paint bitmapPaint;
//文本的大小
private int textSize;
//数据
private List<HourlyResponse.HourlyBean> hourlyWeatherList;
//画虚线的点的index
private List dashLineList;
private int screenWidth;
//每个item的宽度
private int itemWidth;
//温度基准高度
private int lowestTempHeight;
//温度基准高度
private int highestTempHeight;
//最低温
private int lowestTemp;
//最高温
private int highestTemp;
//默认图片绘制位置
float bitmapHeight;
//默认图片宽高
float bitmapXY;
//View宽高
private int mWidth;
private int mHeight;
//默认高
private int defHeightPixel = 0;
private int defWidthPixel = 0;
private int paddingL = 0;
private int paddingT = 0;
private int paddingR = 0;
private int paddingB = 0;
private int mScrollX = 0;
private float baseLineHeight;
private Paint paint1;
private boolean isDark = false;
public HourlyForecastView(Context context) {
this(context, null);
}
public HourlyForecastView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public HourlyForecastView(final Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public HourlyForecastView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
mContext = context;
// if (ContentUtil.APP_SETTING_THEME.equals(“深色”)) {
// isDark = true;
// } else {
// isDark = false;
// }
isDark = false;
initDefValue();
initPaint();
}
private static int ITEM_SIZE = 24;
public void initData(List<HourlyResponse.HourlyBean> weatherData) {
hourlyWeatherList = weatherData;
int size = weatherData.size();
ITEM_SIZE = size;
dashLineList = new ArrayList<>();
Iterator iterator = weatherData.iterator();
HourlyResponse.HourlyBean hourlyBase;
String lastText = “”;
int idx = 0;
while (iterator.hasNext()) {
hourlyBase = (HourlyResponse.HourlyBean) iterator.next();
if (!hourlyBase.getIcon().equals(lastText)) {
if (idx != size - 1) {
dashLineList.add(idx);//从0开始添加虚线位置的索引值idx
lastText = hourlyBase.getIcon();
}
}
idx++;
}
dashLineList.add(size - 1);//添加最后一条虚线位置的索引值idx
invalidate();
}
private void initDefValue() {
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
itemWidth = DisplayUtil.dp2px(mContext, 30);
defWidthPixel = itemWidth * (ITEM_SIZE - 1);
defHeightPixel = DisplayUtil.dp2px(mContext, 80);
lowestTempHeight = DisplayUtil.dp2px(mContext, 40);//长度 非y轴值
highestTempHeight = DisplayUtil.dp2px(mContext, 70);
//defPadding
paddingT = DisplayUtil.dp2px(mContext, 20);
paddingL = DisplayUtil.dp2px(mContext, 10);
paddingR = DisplayUtil.dp2px(mContext, 15);
textSize = DisplayUtil.sp2px(mContext, 12);
bitmapHeight = 1 / 2f * (2 * defHeightPixel - lowestTempHeight) + DisplayUtil.dp2px(mContext, 2);//- 给文字留地方
bitmapXY = 18;
}
private TextPaint textLinePaint;
private void initPaint() {
// setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint1.setColor(mContext.getResources().getColor(R.color.line_back_dark));
paint1.setStyle(Paint.Style.FILL);
foldLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
foldLinePaint.setStyle(Paint.Style.STROKE);
foldLinePaint.setStrokeWidth(5);
//折线颜色
foldLinePaint.setColor(mContext.getResources().getColor(R.color.line_color_2));
backPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backPaint.setStrokeWidth(2);
backPaint.setAntiAlias(true);
dashPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dashPaint.setColor(mContext.getResources().getColor(R.color.back_white));
DashPathEffect pathEffect = new DashPathEffect(new float[]{8, 8, 8, 8}, 1);
dashPaint.setPathEffect(pathEffect);
dashPaint.setStrokeWidth(3);
dashPaint.setAntiAlias(true);
dashPaint.setStyle(Paint.Style.STROKE);
textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(textSize);
textLinePaint = new TextPaint();
textLinePaint.setTextSize(DisplayUtil.sp2px(getContext(), 12));
textLinePaint.setAntiAlias(true);
textLinePaint.setColor(mContext.getResources().getColor(R.color.black));
//底部时间文字颜色
textPaint.setColor(mContext.getResources().getColor(R.color.search_light_un_color));
baseLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
baseLinePaint.setStrokeWidth(3);
baseLinePaint.setStyle(Paint.Style.STROKE);
baseLinePaint.setColor(mContext.getResources().getColor(R.color.slategray));
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapPaint.setFilterBitmap(true);//图像滤波处理
bitmapPaint.setDither(true);//防抖动
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//当设置的padding值小于默认值是设置为默认值
paddingT = DisplayUtil.dp2px(mContext, 20);
paddingL = DisplayUtil.dp2px(mContext, 10);
paddingR = DisplayUtil.dp2px(mContext, 15);
paddingB = Math.max(paddingB, getPaddingBottom());
//获取测量模式
//注意 HorizontalScrollView的子View 在没有明确指定dp值的情况下 widthMode总是MeasureSpec.UNSPECIFIED
//同理 ScrollView的子View的heightMode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取测量大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
mWidth = widthSize + paddingL + paddingR;
mHeight = heightSize;
}
//如果为wrap_content 那么View大小为默认值
if (widthMode == MeasureSpec.UNSPECIFIED && heightMode == MeasureSpec.AT_MOST) {
mWidth = defWidthPixel + paddingL + paddingR;
mHeight = defHeightPixel + paddingT + paddingB;
}
//设置视图的大小
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initDefValue();
initPaint();
if (hourlyWeatherList != null && hourlyWeatherList.size() != 0) {
drawLines(canvas);
drawBitmaps(canvas);
drawTemp(canvas);
}
}
private void drawTemp(Canvas canvas) {
for (int i = 0; i < hourlyWeatherList.size(); i++) {
if (currentItemIndex == i) {
//计算提示文字的运动轨迹
// int Y = getTempBarY(i);
String tmp = hourlyWeatherList.get(i).getTemp();
float temp = Integer.parseInt(tmp);
int Y = (int) (tempHeightPixel(temp) + paddingT);
//画出温度提示
int offset = itemWidth / 4;
Rect targetRect = new Rect(getScrollBarX(), Y - DisplayUtil.dip2px(getContext(), 24)
, getScrollBarX() + offset, Y - DisplayUtil.dip2px(getContext(), 4));
Paint.FontMetricsInt fontMetrics = textLinePaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
textLinePaint.setTextAlign(Paint.Align.LEFT);
// if (ContentUtil.APP_SETTING_UNIT.equals(“hua”)) {
// tmp = String.valueOf(TransUnitUtil.getF(tmp));
// }
canvas.drawText(tmp + “°”, targetRect.centerX(), baseline, textLinePaint);
}
}
}
private void drawBitmaps(Canvas canvas) {
int scrollX = mScrollX;
boolean leftHide;
boolean rightHide;
for (int i = 0; i < dashLineList.size() - 1; i++) {
leftHide = true;
rightHide = true;
int left = itemWidth * dashLineList.get(i) + paddingL;
int right = itemWidth * dashLineList.get(i + 1) + paddingL;
//图的中间位置 drawBitmap是左边开始画
float drawPoint = 0;
if (left > scrollX && left < scrollX + screenWidth) {
leftHide = false;//左边缘显示
}
if (right > scrollX && right < scrollX + screenWidth) {
rightHide = false;
}
if (!leftHide && !rightHide) {//左右边缘都显示
drawPoint = (left + right) / 2f;
} else if (leftHide && !rightHide) {//右边缘与屏幕左边
drawPoint = (scrollX + right) / 2f;
} else if (!leftHide) {//左边缘与屏幕右边
//rightHide is True when reach this statement
drawPoint = (left + screenWidth + scrollX) / 2f;
} else {//左右边缘都不显示
if (right < scrollX + screenWidth) { //左右边缘都在屏幕左边
continue;
} else if (left > scrollX + screenWidth) {//左右边缘都在屏幕右边
continue;
} else {
drawPoint = (screenWidth) / 2f + scrollX;
}
}
String code = hourlyWeatherList.get(dashLineList.get(i)).getIcon();
BitmapDrawable bd;
if (code.contains(“d”)) {
bd = (BitmapDrawable) mContext.getResources().getDrawable(IconUtils.getDayIconDark(code.replace(“d”, “”)));
} else {
bd = (BitmapDrawable) mContext.getResources().getDrawable(IconUtils.getNightIconDark(code.replace(“n”, “”)));
}
assert bd != null;
Bitmap bitmap = DisplayUtil.bitmapResize(bd.getBitmap(),
DisplayUtil.dp2px(mContext, bitmapXY), DisplayUtil.dp2px(mContext, bitmapXY));
//越界判断
if (drawPoint >= right - bitmap.getWidth() / 2f) {
drawPoint = right - bitmap.getWidth() / 2f;
}
if (drawPoint <= left + bitmap.getWidth() / 2f) {
drawPoint = left + bitmap.getWidth() / 2f;
}
drawBitmap(canvas, bitmap, drawPoint, bitmapHeight);
// String text = hourlyWeatherList.get(dashLineList.get(i)).getCond_txt();
// textPaint.setTextSize(DisplayUtil.sp2px(mContext, 8));
// canvas.drawText(text, drawPoint, bitmapHeight + bitmap.getHeight() + 100 / 3f, textPaint);
}
}
private void drawBitmap(Canvas canvas, Bitmap bitmap, float left, float top) {
canvas.save();
canvas.drawBitmap(bitmap, left - bitmap.getWidth() / 2, top, bitmapPaint);
canvas.restore();
}
private void drawLines(Canvas canvas) {
//底部的线的高度 高度为控件高度减去text高度的1.5倍
baseLineHeight = mHeight - 1.5f * textSize;
Path path = new Path();
List dashWidth = new ArrayList<>();
List dashHeight = new ArrayList<>();
List mPointList = new ArrayList<>();
for (int i = 0; i < hourlyWeatherList.size(); i++) {
float temp = Integer.parseInt(hourlyWeatherList.get(i).getTemp());
float w = itemWidth * i + paddingL;
float h = tempHeightPixel(temp) + paddingT;
Point point = new Point((int) w, (int) h);
mPointList.add(point);
//画虚线
if (dashLineList.contains(i)) {
dashWidth.add(w);
dashHeight.add(h);
}
}
float prePreviousPointX = Float.NaN;
float prePreviousPointY = Float.NaN;
float previousPointX = Float.NaN;
float previousPointY = Float.NaN;
float currentPointX = Float.NaN;
float currentPointY = Float.NaN;
float nextPointX;
float nextPointY;
for (int valueIndex = 0; valueIndex < hourlyWeatherList.size(); ++valueIndex) {
if (Float.isNaN(currentPointX)) {
Point point = mPointList.get(valueIndex);
currentPointX = point.x;
currentPointY = point.y;
}
if (Float.isNaN(previousPointX)) {
//是否是第一个点
if (valueIndex > 0) {
Point point = mPointList.get(valueIndex - 1);
previousPointX = point.x;
previousPointY = point.y;
} else {
//是的话就用当前点表示上一个点
previousPointX = currentPointX;
previousPointY = currentPointY;
}
}
if (Float.isNaN(prePreviousPointX)) {
//是否是前两个点
if (valueIndex > 1) {
Point point = mPointList.get(valueIndex - 2);
prePreviousPointX = point.x;
prePreviousPointY = point.y;
} else {
//是的话就用当前点表示上上个点
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
}
}
// 判断是不是最后一个点了
if (valueIndex < hourlyWeatherList.size() - 1) {
Point point = mPointList.get(valueIndex + 1);
nextPointX = point.x;
nextPointY = point.y;
} else {
//是的话就用当前点表示下一个点
nextPointX = currentPointX;
nextPointY = currentPointY;
}
if (valueIndex == 0) {
// 将Path移动到开始点
path.moveTo(currentPointX, currentPointY);
} else {
// 求出控制点坐标
final float firstDiffX = (currentPointX - prePreviousPointX);
final float firstDiffY = (currentPointY - prePreviousPointY);
final float secondDiffX = (nextPointX - previousPointX);
final float secondDiffY = (nextPointY - previousPointY);
final float firstControlPointX = previousPointX + (0.2F * firstDiffX);
final float firstControlPointY = previousPointY + (0.2F * firstDiffY);
final float secondControlPointX = currentPointX - (0.2F * secondDiffX);
final float secondControlPointY = currentPointY - (0.2F * secondDiffY);
//画出曲线
path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,
currentPointX, currentPointY);
}
// 更新值,
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
previousPointX = currentPointX;
previousPointY = currentPointY;
currentPointX = nextPointX;
currentPointY = nextPointY;
}
//画折线
canvas.drawPath(path, foldLinePaint);
path.lineTo(mWidth - paddingR, baseLineHeight);
path.lineTo(paddingL, baseLineHeight);
//画阴影
int[] shadeColors = new int[]{
Color.argb(100, 60, 174, 242),
Color.argb(30, 60, 174, 242),
Color.argb(18, 237, 238, 240)};
Shader mShader = new LinearGradient(0, 0, 0, getHeight(), shadeColors, null, Shader.TileMode.CLAMP);
backPaint.setShader(mShader);
canvas.drawPath(path, backPaint);
//画虚线
drawDashLine(dashWidth, dashHeight, canvas);
for (int i = 0; i < hourlyWeatherList.size(); i++) {
float temp = Integer.parseInt(hourlyWeatherList.get(i).getTemp());
float w = itemWidth * i + paddingL;
float h = tempHeightPixel(temp) + paddingT;
//画时间
String time = hourlyWeatherList.get(i).getFxTime();
//画时间
if (ITEM_SIZE > 8) {
if (i % 2 == 0) {
if (i == 0) {
textPaint.setTextAlign(Paint.Align.LEFT);
} else {
textPaint.setTextAlign(Paint.Align.CENTER);
}
canvas.drawText(time.substring(time.length() - 11, time.length() - 6), w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);
}
} else {
textPaint.setTextAlign(Paint.Align.CENTER);
if (i == 0) {
canvas.drawText(“. 现在”, w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);
} else {
canvas.drawText(time.substring(time.length() - 11, time.length() - 6), w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);
}
}
}
}
//画虚线
private void drawDashLine(List dashWidth, List dashHeight, Canvas canvas) {
if (dashHeight != null && dashHeight.size() > 1) {
for (int i = 1; i < dashHeight.size() - 1; i++) {
canvas.drawLine(dashWidth.get(i), dashHeight.get(i) + 3, dashWidth.get(i), baseLineHeight, dashPaint);
}
}
}
public float tempHeightPixel(float tmp) {
float res = ((tmp - lowestTemp) / (highestTemp - lowestTemp)) * (highestTempHeight - lowestTempHeight) + lowestTempHeight;
return defHeightPixel - res;//y从上到下
}
@Override
public void update(int scrollX) {
mScrollX = scrollX;
}
public void setLowestTemp(int lowestTemp) {
this.lowestTemp = lowestTemp;
}
public void setHighestTemp(int highestTemp) {
this.highestTemp = highestTemp;
}
private int maxScrollOffset = 0;//滚动条最长滚动距离
private int scrollOffset = 0; //滚动条偏移量
private int currentItemIndex = 0; //当前滚动的位置所对应的item下标
//设置scrollerView的滚动条的位置,通过位置计算当前的时段
public void setScrollOffset(int offset, int maxScrollOffset) {
this.maxScrollOffset = maxScrollOffset + DisplayUtil.dp2px(mContext, 50);
scrollOffset = offset;
currentItemIndex = calculateItemIndex();
invalidate();
}
//通过滚动条偏移量计算当前选择的时刻
private int calculateItemIndex() {
int x = getScrollBarX();
int sum = paddingL - itemWidth / 2;
for (int i = 0; i < ITEM_SIZE - 1; i++) {
sum += itemWidth;
if (x < sum)
return i;
}
return ITEM_SIZE - 1;
}
private int getScrollBarX() {
int x = (ITEM_SIZE - 1) * itemWidth * scrollOffset / maxScrollOffset;
x = x - DisplayUtil.dp2px(mContext, 3);
return x;
}
}
IndexHorizontalScrollView.java
package com.llw.goodweather.view.horizonview;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.HorizontalScrollView;
import com.llw.goodweather.utils.DisplayUtil;
/**
- 横向滑动条
*/
public class IndexHorizontalScrollView extends HorizontalScrollView {
private HourlyForecastView hourlyForecastView;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
文末
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
EM_SIZE - 1) * itemWidth * scrollOffset / maxScrollOffset;
x = x - DisplayUtil.dp2px(mContext, 3);
return x;
}
}
IndexHorizontalScrollView.java
package com.llw.goodweather.view.horizonview;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.HorizontalScrollView;
import com.llw.goodweather.utils.DisplayUtil;
/**
- 横向滑动条
*/
public class IndexHorizontalScrollView extends HorizontalScrollView {
private HourlyForecastView hourlyForecastView;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-0aWlnBHJ-1712605715048)]
[外链图片转存中…(img-PdPegIe8-1712605715049)]
[外链图片转存中…(img-moQa1bYH-1712605715049)]
[外链图片转存中…(img-pRSIPaW7-1712605715049)]
[外链图片转存中…(img-NhVaYgQK-1712605715049)]
[外链图片转存中…(img-ztXbdZ5D-1712605715050)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-gdbsPjfv-1712605715050)]
文末
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-LTwUEPj0-1712605715050)]
【算法合集】
[外链图片转存中…(img-HYpspDF0-1712605715051)]
【延伸Android必备知识点】
[外链图片转存中…(img-KCXzSaWV-1712605715051)]
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-Gsx3exDl-1712605715051)]