if (isOpenLocationServiceEnable()) {
tvCity.setEnabled(false);//不可点击
startLocation();//开始定位
} else {
tvCity.setEnabled(true);//可以点击
ToastUtils.showShortToast(context, “(((φ(◎ロ◎;)φ))),你好像忘记打开定位功能了”);
tvCity.setText(“打开定位”);
}
代码的意思也很明显开了定位就去定位,没有开定位就提示你一下顺便把文本改成打开定位。
然后设置一个全局的标识
private int OPEN_LOCATION = 9527;//进入手机定位设置页面标识
然后为这个tvCity添加点击事件,在点击的时候还是要判断是否有打开定位权限
如果没有权限也不让进入地图天气页面
startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), OPEN_LOCATION);
通过startActivityForResult跳转到系统定位设置页面时传递了一个请求码过去,然后当返回时用这个请求做验证就知道是不是当前的页面,然后再返回里面做判断,重写onActivityResult方法,代码如下。
/**
-
返回Activity的结果
-
@param requestCode
-
@param resultCode
-
@param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == OPEN_LOCATION) {//则是从手机定位页面返回
if (isOpenLocationServiceEnable()) {//已打开
tvCity.setText(“重新定位”);
tvCity.setEnabled(true);//可以点击
}else {
ToastUtils.showShortToast(context,“有意思吗?你跳过去又不打开定位,玩呢?嗯?我也是有脾气的好伐!”);
tvCity.setText(“打开定位”);
tvCity.setEnabled(false);//不可点击
}
}
super.onActivityResult(requestCode, resultCode, data);
}
在返回里面再做一次判断,因为你跳过去并不代表你就打开了定位,对于这种跳过去不打开定位的用户,我表示强烈谴责和抗议,同时为了不出现这种功能性上的BUG,我在定位的监听回调中也做了一下处理
这样就行成了一个闭环。
OK我们运行一下吧
这样就可以了。
下面就是在地图天气中增加逐小时天气了,这里我用了和风的自定义View,感觉还是不错的,下面来一步一步实现吧。首先在我既然要显示这个数据就要先获取,打开MapWeatherContract,在里面新增如下代码:
/**
-
24小时天气预报
-
@param location 城市名
*/
public void weatherHourly(String location){//这个3 表示使用新的V7API访问地址
ApiService service = ServiceGenerator.createService(ApiService.class,3);
service.hourlyWeather(location).enqueue(new NetCallBack() {
@Override
public void onSuccess(Call call, Response response) {
if(getView() != null){
getView().getWeatherHourlyResult(response);
}
}
@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;
public IndexHorizontalScrollView(Context context) {
this(context, null);
}
public IndexHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndexHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
-
绘制
-
@param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int offset = computeHorizontalScrollOffset();
int maxOffset = computeHorizontalScrollRange() - DisplayUtil.getScreenWidth(getContext());
if(hourlyForecastView != null){
hourlyForecastView.setScrollOffset(offset, maxOffset);
}
}
/**
-
设置24小时的View
-
@param today24HourView
*/
public void setToday24HourView(HourlyForecastView today24HourView){
this.hourlyForecastView = today24HourView;
}
}
里面还有一些颜色值,在mvplibrary下的colors.xml,如下
<?xml version="1.0" encoding="utf-8"?>#C6D7F4
#FBFEF7
#ffffff
#000000
#1E1E1A
#77d034
#9FC8E9
#00000000
#22000000
#22FFFFFF
#F38A50
#FFEFD5
#454545
#BABABA
#FFBCB3
#FDEBE8
#E7F3FC
#F2F2F2
#707070
#6D6D6D
#FF0A00
#E3E5E8
#E7C373
#243440
#C8DCFF
#F8F8F8
#44000000
#66000000
#70000000
#88000000
#FF7E45
#B3BCCA
#22FFFFFF
#44FFFFFF
#66FFFFFF
#88FFFFFF
#56004f
#F6F6F6
#626262
#141414
#DEDEE1
#D9D9D9
#818181
#f7f8fa
#E9EAEF
#B9C0CA
#3f8DA0BA
#919191
#666666
#708090
#50A0FF
#002C46
#597EF7
#33F2BE55
#33446FF2
#33AD9442
#334A5CC4
#A4A4A4
#3C91F2
#595959
然后在activity_map_weather.xml中
这一部分布局代码如下:
<TextView
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“@dimen/dp_12”
android:text=“24小时预报”
android:textColor=“@color/black_4”
android:textSize=“@dimen/sp_16” />
<RelativeLayout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginBottom=“@dimen/dp_12”>
<TextView
android:id=“@+id/tv_line_max_tmp”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“@dimen/dp_12”
android:layout_marginTop=“@dimen/dp_16”
android:text=“21°”
android:textColor=“@color/black_4”
android:textSize=“@dimen/sp_16” />
<TextView
android:id=“@+id/tv_line_min_tmp”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“@dimen/dp_12”
android:layout_marginTop=“@dimen/dp_66”
android:text=“11°”
android:textColor=“@color/black_4”
android:textSize=“@dimen/dp_16” />
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“@dimen/dp_98”
android:layout_marginLeft=“@dimen/dp_40”
android:orientation=“horizontal”
android:paddingRight=“@dimen/dp_12”>
<com.llw.goodweather.view.horizonview.IndexHorizontalScrollView
android:id=“@+id/hsv”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”>
<com.llw.goodweather.view.horizonview.HourlyForecastView
android:id=“@+id/hourly”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content” />
</com.llw.goodweather.view.horizonview.IndexHorizontalScrollView>
然后在MapWeatherActivity中
@BindView(R.id.tv_line_max_tmp)
TextView tvLineMaxTmp;//今日最高温
@BindView(R.id.tv_line_min_tmp)
TextView tvLineMinTmp;//今日最低温
@BindView(R.id.hourly)
HourlyForecastView hourly;//和风自定义逐小时天气渲染控件
@BindView(R.id.hsv)
IndexHorizontalScrollView hsv;//和风自定义滚动条
在搜索城市的返回中,新增一个24小时天气预报请求
然后在请求返回中做处理
然后实现里面的一个构造方法
/**
-
24小时天气预报数据返回
-
@param response
*/
@Override
public void getWeatherHourlyResult(Response response) {
if(response.body().getCode().equals(Constant.SUCCESS_CODE)){
List<HourlyResponse.HourlyBean> hourlyWeatherList = response.body().getHourly();
List<HourlyResponse.HourlyBean> data = new ArrayList<>();
if (hourlyWeatherList.size() > 23) {
for (int i = 0; i < 24; i++) {
data.add(hourlyWeatherList.get(i));
String condCode = data.get(i).getIcon();
String time = data.get(i).getFxTime();
time = time.substring(time.length() - 11, time.length() - 9);
int hourNow = Integer.parseInt(time);
if (hourNow >= 6 && hourNow <= 19) {
data.get(i).setIcon(condCode + “d”);
} else {
data.get(i).setIcon(condCode + “n”);
}
}
} else {
for (int i = 0; i < hourlyWeatherList.size(); i++) {
data.add(hourlyWeatherList.get(i));
String condCode = data.get(i).getIcon();
String time = data.get(i).getFxTime();
time = time.substring(time.length() - 11, time.length() - 9);
int hourNow = Integer.parseInt(time);
if (hourNow >= 6 && hourNow <= 19) {
data.get(i).setIcon(condCode + “d”);
} else {
data.get(i).setIcon(condCode + “n”);
}
}
}
int minTmp = Integer.parseInt(data.get(0).getTemp());
int maxTmp = minTmp;
for (int i = 0; i < data.size(); i++) {
int tmp = Integer.parseInt(data.get(i).getTemp());
minTmp = Math.min(tmp, minTmp);
maxTmp = Math.max(tmp, maxTmp);
}
//设置当天的最高最低温度
hourly.setHighestTemp(maxTmp);
hourly.setLowestTemp(minTmp);
if (maxTmp == minTmp) {
hourly.setLowestTemp(minTmp - 1);
}
hourly.initData(data);
tvLineMaxTmp.setText(maxTmp + “°”);
tvLineMinTmp.setText(minTmp + “°”);
}else {
ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getCode()));
}
}
OK,然后运行一下:
首先定义样式,在styles.xml中增加
然后在view包下新增一个
SunView.java
package com.llw.goodweather.view.skyview;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.llw.goodweather.R;
import com.llw.goodweather.utils.DisplayUtil;
import java.text.DecimalFormat;
/**
- 太阳、月亮自定义View
*/
public class SunView extends View {
private int mWidth; //屏幕宽度
private int marginTop = 50;//离顶部的高度
private int mCircleColor; //圆弧颜色
private int mFontColor; //字体颜色
private int mRadius; //圆的半径
private float mCurrentAngle; //当前旋转的角度
private float mTotalMinute; //总时间(日落时间减去日出时间的总分钟数)
private float mNeedMinute; //当前时间减去日出时间后的总分钟数
private float mPercentage; //根据所给的时间算出来的百分占比
private float positionX, positionY; //太阳图片的x、y坐标
private float mFontSize; //字体大小
private String mStartTime; //开始时间(日出时间)
private String mEndTime; //结束时间(日落时间)
private String mCurrentTime; //当前时间
private Paint mTextPaint; //画笔
private Paint mLinePaint; //画笔
private Paint mTimePaint; //画笔
private RectF mRectF; //半圆弧所在的矩形
private Bitmap mSunIcon; //太阳图片
private WindowManager wm;
private Paint mShadePaint;
private Paint mPathPaint;
private Context mContext;
private boolean isSun = true;
private float endHour;
private Paint shadePaint;
private Paint pathPaint;
public SunView(Context context) {
this(context, null);
}
public SunView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SunView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, @Nullable AttributeSet attrs) {
mContext = context;
marginTop = DisplayUtil.dip2px(context, 30);
@SuppressLint(“CustomViewStyleable”) TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.SunAnimationView);
mCircleColor = type.getColor(R.styleable.SunAnimationView_sun_circle_color, getContext().getResources().getColor(R.color.dark_text_color));
mFontColor = type.getColor(R.styleable.SunAnimationView_sun_font_color, getContext().getResources().getColor(R.color.colorAccent));
mRadius = type.getInteger(R.styleable.SunAnimationView_sun_circle_radius, DisplayUtil.dp2px(getContext(), 130));
mRadius = DisplayUtil.dp2px(getContext(), mRadius);
mFontSize = type.getDimension(R.styleable.SunAnimationView_sun_font_size, DisplayUtil.dp2px(getContext(), 10));
mFontSize = DisplayUtil.dp2px(getContext(), mFontSize);
isSun = type.getBoolean(R.styleable.SunAnimationView_type, true);
type.recycle();
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTimePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
shadePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/781dc5f2854b1a8bea9b2449e4af0b26.jpeg)
Android高级架构师
由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。
- 330页PDF Android学习核心笔记(内含上面8大板块)
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
- Android BAT部分大厂面试题(有解析)
好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
private String mCurrentTime; //当前时间
private Paint mTextPaint; //画笔
private Paint mLinePaint; //画笔
private Paint mTimePaint; //画笔
private RectF mRectF; //半圆弧所在的矩形
private Bitmap mSunIcon; //太阳图片
private WindowManager wm;
private Paint mShadePaint;
private Paint mPathPaint;
private Context mContext;
private boolean isSun = true;
private float endHour;
private Paint shadePaint;
private Paint pathPaint;
public SunView(Context context) {
this(context, null);
}
public SunView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SunView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, @Nullable AttributeSet attrs) {
mContext = context;
marginTop = DisplayUtil.dip2px(context, 30);
@SuppressLint(“CustomViewStyleable”) TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.SunAnimationView);
mCircleColor = type.getColor(R.styleable.SunAnimationView_sun_circle_color, getContext().getResources().getColor(R.color.dark_text_color));
mFontColor = type.getColor(R.styleable.SunAnimationView_sun_font_color, getContext().getResources().getColor(R.color.colorAccent));
mRadius = type.getInteger(R.styleable.SunAnimationView_sun_circle_radius, DisplayUtil.dp2px(getContext(), 130));
mRadius = DisplayUtil.dp2px(getContext(), mRadius);
mFontSize = type.getDimension(R.styleable.SunAnimationView_sun_font_size, DisplayUtil.dp2px(getContext(), 10));
mFontSize = DisplayUtil.dp2px(getContext(), mFontSize);
isSun = type.getBoolean(R.styleable.SunAnimationView_type, true);
type.recycle();
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTimePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
shadePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-yprdhEda-1711767488411)]
[外链图片转存中…(img-bBvb1AFB-1711767488412)]
[外链图片转存中…(img-Xw2iy9Si-1711767488412)]
[外链图片转存中…(img-MzY8kW6R-1711767488412)]
[外链图片转存中…(img-tzcpuWyM-1711767488412)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/781dc5f2854b1a8bea9b2449e4af0b26.jpeg)
Android高级架构师
由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。
- 330页PDF Android学习核心笔记(内含上面8大板块)
[外链图片转存中…(img-x4gdSzZZ-1711767488413)]
[外链图片转存中…(img-I1tchtMd-1711767488413)]
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
[外链图片转存中…(img-bt4xyOlo-1711767488413)]
- Android BAT部分大厂面试题(有解析)
[外链图片转存中…(img-pN1WBWAg-1711767488413)]
好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。