Android 天气APP(二十六)增加自动更新(检查版本、通知栏下载、自动安装)

public int getFsize() {

return fsize;

}

public void setFsize(int fsize) {

this.fsize = fsize;

}

}

}

然后改动一下litepal.xml配置文件

在这里插入图片描述

数据表有了,下面就是要请求数据了,所以要新增一个API接口,打开ServiceGenerator,新增如下,我将之前的测试地址做了一个拆分,这样规范管理,虽说有些麻烦。

case 5://APP更新

BASE_URL = “http://api.bq04.com”;//Fr.im更新

break;

在这里插入图片描述

然后打开ApiService,新增接口方法,因为里面的id和api token是固定的所以我就不用动态传递过去了,直接写死在url里面,当然如果你要写的话记得要用自己的id和api token,用我的你是那不到返回数据的。

/**

  • APP版本更新

*/

@GET(“/apps/latest/你的id?api_token=你的API Token”)

Call getAppInfo();

在这里插入图片描述

那么下面就要写一个订阅器了,我的想法是在应用欢迎页就请求数据。那么就在app下的contract包下创建一个SplashContract,里面的代码如下:

package com.llw.goodweather.contract;

import com.llw.goodweather.api.ApiService;

import com.llw.goodweather.bean.NewSearchCityResponse;

import com.llw.goodweather.bean.NowResponse;

import com.llw.mvplibrary.base.BasePresenter;

import com.llw.mvplibrary.base.BaseView;

import com.llw.mvplibrary.bean.AppVersion;

import com.llw.mvplibrary.net.NetCallBack;

import com.llw.mvplibrary.net.ServiceGenerator;

import retrofit2.Call;

import retrofit2.Response;

/**

  • 欢迎页订阅器

*/

public class SplashContract {

public static class SplashPresenter extends BasePresenter {

/**

  • 获取最新的APP版本信息

*/

public void getAppInfo() {//注意这里的4表示新的搜索城市地址接口

ApiService service = ServiceGenerator.createService(ApiService.class, 5);

service.getAppInfo().enqueue(new NetCallBack() {

@Override

public void onSuccess(Call call, Response response) {

if(getView() != null){

getView().getAppInfoResult(response);

}

}

@Override

public void onFailed() {

if(getView() != null){

getView().getDataFailed();

}

}

});

}

}

public interface ISplashView extends BaseView {

//APP信息返回

void getAppInfoResult(Response response);

//错误返回

void getDataFailed();

}

}

都是属于基本操作了,不需要解释了。

然后打开SplashActivity

在这里插入图片描述

增加请求

在这里插入图片描述

实现如下三个方法

@Override

protected SplashContract.SplashPresenter createPresent() {

return new SplashContract.SplashPresenter();

}

/**

  • 获取APP最新版本信息返回

  • @param response

*/

@Override

public void getAppInfoResult(Response response) {

if (response.body() != null) {

AppVersion appVersion = new AppVersion();

appVersion.setName(response.body().getName());//应用名称

appVersion.setVersion(response.body().getVersion());//应用版本 对应code

appVersion.setVersionShort(response.body().getVersionShort());//应用版本名

appVersion.setChangelog(response.body().getChangelog());//更新日志

appVersion.setUpdate_url(response.body().getUpdate_url());//更新地址

appVersion.setInstall_url(response.body().getInstall_url());//安装地址

appVersion.setAppSize(String.valueOf(response.body().getBinary().getFsize()));//APK大小

//添加数据前先判断是否已经有数据了

if (LitePal.find(AppVersion.class, 1) != null){

appVersion.update(1);//更新数据

}else {

appVersion.save();//保存添加数据

}

}

}

@Override

public void getDataFailed() {

Log.d(“Network Error”,“网络异常”);

}

现在这个数据就已经有了并且也储存起来了,下一步就是检测这个版本的更新了。

4.检查版本更新、自定义更新提示弹窗


这里我写了两个工具类,代码如下

APKVersionInfoUtils .java

package com.llw.goodweather.utils;

import android.content.Context;

import android.content.pm.PackageManager;

/**

  • Created by chenxi on 2019/4/29.

  • APP版本信息获取工具类

*/

public class APKVersionInfoUtils {

/**

  • 获取当前本地apk的版本

  • @param mContext

  • @return

*/

public static int getVersionCode(Context mContext) {

int versionCode = 0;

try {

//获取软件版本号,对应AndroidManifest.xml下android:versionCode

versionCode = mContext.getPackageManager().

getPackageInfo(mContext.getPackageName(), 0).versionCode;

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

return versionCode;

}

/**

  • 获取版本号名称

  • @param context 上下文

  • @return

*/

public static String getVerName(Context context) {

String verName = “”;

try {

verName = context.getPackageManager().

getPackageInfo(context.getPackageName(), 0).versionName;

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

return verName;

}

}

AppStartUpUtils .java

package com.llw.goodweather.utils;

import android.content.Context;

import java.text.SimpleDateFormat;

import java.util.Date;

/**

  • APP启动判断工具类

*/

public class AppStartUpUtils {

/**

  • 判断是否是首次启动

  • @param context

  • @return

*/

public static boolean isFirstStartApp(Context context) {

Boolean isFirst = SPUtils.getBoolean(Constant.APP_FIRST_START, true, context);

if (isFirst) {// 第一次

SPUtils.putBoolean(Constant.APP_FIRST_START, false, context);

return true;

} else {

return false;

}

}

/**

  • 判断是否是今日首次启动APP

  • @param context

  • @return

*/

public static boolean isTodayFirstStartApp(Context context) {

String saveDate = SPUtils.getString(Constant.START_UP_APP_TIME, “2020-08-27”, context);

String todayDate = new SimpleDateFormat(“yyyy-MM-dd”).format(new Date());

if (!saveDate.equals(todayDate)) {//第一次打开

SPUtils.putString(Constant.START_UP_APP_TIME, todayDate, context);

return true;

} else {

return false;

}

}

}

第二个工具类里面用到了一些全局的参数,如下:

package com.llw.goodweather.utils;

/**

  • 统一管理缓存中对应的KEY

*/

public class Constant {

public final static String SUCCESS_CODE = “200”;

public final static String CITY = “city”;//市

public final static String DISTRICT = “district”;//区/县

public final static String LOCATION_ID = “locationId”;//通过搜索接口得到的城市id,在V7中所有数据通过id来查询

public final static String EVERYDAY_IMG = “everyday_img”;//每日图片开关

public final static String IMG_LIST = “img_list”;//图片列表开关

public final static String CUSTOM_IMG = “custom_img”;//手动定义开关

public final static String IMG_POSITION = “img_position”;//选中的本地背景图片

public final static int SELECT_PHOTO = 2;//启动相册标识

public final static String CUSTOM_IMG_PATH = “custom_img_path”;//手动上传图片地址

public final static String FLAG_OTHER_RETURN=“flag_other_return”;//跳转页面的标识

public final static String LOCATION=“location”;

public final static String APP_FIRST_START = “appFirstStart”;//App首次启动

public final static String START_UP_APP_TIME = “startAppTime”;//今日启动APP的时间

}

然后在MainActivity中就可以进行判断了

/**

  • 检查APP版本

*/

private void checkAppVersion() {

AppVersion appVersion = LitePal.find(AppVersion.class,1);

if(!appVersion.getVersionShort().equals(APKVersionInfoUtils.getVerName(context))){//提示更新

if(AppStartUpUtils.isTodayFirstStartApp(context)){//今天第一次打开APP

//更新提示弹窗

}

}

}

下面来写这个更新提示弹窗,我就把我好久之前写的放上来了。

首先增加样式文件,在mvplibrary下面的styles.xml中

在这里插入图片描述

里面还有6个动画文件。如下:

在anim下创建

dialog_from_bottom_anim_in.xml

<?xml version="1.0" encoding="utf-8"?>

<translate

android:duration=“400”

android:fromXDelta=“0”

android:fromYDelta=“1000”

android:toXDelta=“0”

android:toYDelta=“0” />

dialog_from_bottom_anim_out.xml

<?xml version="1.0" encoding="utf-8"?>

<translate

android:duration=“400”

android:fromXDelta=“0”

android:fromYDelta=“0”

android:toXDelta=“0”

android:toYDelta=“1000” />

dialog_from_top_anim_in.xml

<?xml version="1.0" encoding="utf-8"?>

<translate

android:duration=“1000”

android:fromYDelta=“-100%”

android:toYDelta=“0” />

dialog_from_top_anim_out.xml

<?xml version="1.0" encoding="utf-8"?>

<translate

android:duration=“1000”

android:fromYDelta=“0”

android:toYDelta=“-100%” />

dialog_scale_anim_in.xml

<?xml version="1.0" encoding="utf-8"?>

<scale

android:duration=“135”

android:fromXScale=“0.8”

android:fromYScale=“0.8”

android:pivotX=“50%”

android:pivotY=“50%”

android:toXScale=“1.05”

android:toYScale=“1.05” />

<scale

android:duration=“105”

android:fromXScale=“1.05”

android:fromYScale=“1.05”

android:pivotX=“50%”

android:pivotY=“50%”

android:startOffset=“135”

android:toXScale=“0.95”

android:toYScale=“0.95” />

<scale

android:duration=“60”

android:fromXScale=“0.95”

android:fromYScale=“0.95”

android:pivotX=“50%”

android:pivotY=“50%”

android:startOffset=“240”

android:toXScale=“1.0”

android:toYScale=“1.0” />

<alpha

android:duration=“90”

android:fromAlpha=“0.0”

android:interpolator=“@android:anim/accelerate_interpolator”

android:toAlpha=“1.0” />

dialog_scale_anim_out.xml

<?xml version="1.0" encoding="utf-8"?>

<scale

android:duration=“150”

android:fromXScale=“1.0”

android:fromYScale=“1.0”

android:pivotX=“50%”

android:pivotY=“50%”

android:toXScale=“0.6”

android:toYScale=“0.6” />

<alpha

android:duration=“150”

android:fromAlpha=“1.0”

android:interpolator=“@android:anim/accelerate_interpolator”

android:toAlpha=“0.0” />

下面就是关于自定义Dialog的内容了。

在这里插入图片描述

在view下新增一个dialog文件夹,然后一一创建下面的文件

AlertDialog.java

package com.llw.mvplibrary.view.dialog;

import android.app.Dialog;

import android.content.Context;

import android.graphics.Bitmap;

import android.view.Gravity;

import android.view.View;

import android.view.ViewGroup;

import androidx.annotation.NonNull;

import androidx.annotation.StyleRes;

import com.llw.mvplibrary.R;

/**

  • 自定义弹窗

*/

public class AlertDialog extends Dialog {

private AlertController mAlert;

public AlertDialog(@NonNull Context context, @StyleRes int themeResId) {

super(context, themeResId);

mAlert = new AlertController(this, getWindow());

}

public void setText(int viewId, CharSequence text) {

mAlert.setText(viewId, text);

}

public T getView(int viewId) {

return mAlert.getView(viewId);

}

public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {

mAlert.setOnClickListener(viewId, onClickListener);

}

//----------------------------------------------------------------------------------------------

public static class Builder {

private final AlertController.AlertParams P;

public Builder(Context context) {

this(context, R.style.dialog);

}

public Builder(Context context, int themeResId) {

P = new AlertController.AlertParams(context, themeResId);

}

/**

  • 设置对话框布局

  • @param view

  • @return

*/

public Builder setContentView(View view) {

P.mView = view;

P.mLayoutResId = 0;

return this;

}

/**

  • @param layoutId

  • @return

*/

public Builder setContentView(int layoutId) {

P.mView = null;

P.mLayoutResId = layoutId;

return this;

}

/**

  • 设置文本

  • @param viewId

  • @param text

  • @return

*/

public Builder setText(int viewId, CharSequence text) {

P.mTextArray.put(viewId, text);

return this;

}

/**

  • 设置文本颜色

  • @param viewId

  • @param color

  • @return

*/

public Builder setTextColor(int viewId, int color) {

P.mTextColorArray.put(viewId, color);

return this;

}

/**

  • 设置图标

  • @param iconId

  • @return

*/

public Builder setIcon(int iconId, int resId) {

P.mIconArray.put(iconId, resId);

return this;

}

/**

  • 设置图片

  • @param viewId

  • @return

*/

public Builder setBitmap(int viewId, Bitmap bitmap) {

P.mBitmapArray.put(viewId, bitmap);

return this;

}

/**

  • 设置对话框宽度占满屏幕

  • @return

*/

public Builder fullWidth() {

P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;

return this;

}

/**

  • 对话框底部弹出

  • @param isAnimation

  • @return

*/

public Builder fromBottom(boolean isAnimation) {

if (isAnimation) {

P.mAnimation = R.style.dialog_from_bottom_anim;

}

P.mGravity = Gravity.BOTTOM;

return this;

}

/**

  • 对话框右部弹出

  • @param isAnimation

  • @return

*/

public Builder fromRight(boolean isAnimation) {

if (isAnimation) {

P.mAnimation = R.style.dialog_from_bottom_anim;

}

P.mGravity = Gravity.RIGHT;

return this;

}

/**

  • 设置对话框宽高

  • @param width

  • @param heigth

  • @return

*/

public Builder setWidthAndHeight(int width, int heigth) {

P.mWidth = width;

P.mHeight = heigth;

return this;

}

/**

  • 设置对话框宽高

  • @param width

  • @param heigth

  • @return

*/

public Builder setWidthAndHeightMargin(int width, int heigth, int heightMargin, int widthMargin) {

P.mWidth = width;

P.mHeight = heigth;

P.mHeightMargin = heightMargin;

P.mWidthMargin = widthMargin;

return this;

}

/**

  • 添加默认动画

  • @return

*/

public Builder addDefaultAnimation() {

P.mAnimation = R.style.dialog_scale_anim;

return this;

}

/**

  • 设置动画

  • @param styleAnimation

  • @return

*/

public Builder setAnimation(int styleAnimation) {

P.mAnimation = styleAnimation;

return this;

}

/**

  • 设置点击事件

  • @param viewId

  • @param onClickListener

  • @return

*/

public Builder setOnClickListener(int viewId, View.OnClickListener onClickListener) {

P.mClickArray.put(viewId, onClickListener);

return this;

}

public Builder setOnLongClickListener(int viewId, View.OnLongClickListener onLongClickListener) {

P.mLondClickArray.put(viewId, onLongClickListener);

return this;

}

/**

  • Sets whether the dialog is cancelable or not. Default is true.

  • @return This Builder object to allow for chaining of calls to set methods

*/

public Builder setCancelable(boolean cancelable) {

P.mCancelable = cancelable;

return this;

}

public Builder setOnCancelListener(OnCancelListener onCancelListener) {

P.mOnCancelListener = onCancelListener;

return this;

}

public Builder setOnDismissListener(OnDismissListener onDismissListener) {

P.mOnDismissListener = onDismissListener;

return this;

}

public Builder setOnKeyListener(OnKeyListener onKeyListener) {

P.mOnKeyListener = onKeyListener;

return this;

}

public AlertDialog create() {

// Context has already been wrapped with the appropriate theme.

final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);

P.apply(dialog.mAlert);

dialog.setCancelable(P.mCancelable);

if (P.mCancelable) {

dialog.setCanceledOnTouchOutside(true);

}

dialog.setOnCancelListener(P.mOnCancelListener);

dialog.setOnDismissListener(P.mOnDismissListener);

if (P.mOnKeyListener != null) {

dialog.setOnKeyListener(P.mOnKeyListener);

}

return dialog;

}

public AlertDialog show() {

final AlertDialog dialog = create();

dialog.show();

return dialog;

}

}

}

AlertController.java

package com.llw.mvplibrary.view.dialog;

import android.content.Context;

import android.content.DialogInterface;

import android.graphics.Bitmap;

import android.util.SparseArray;

import android.view.Gravity;

import android.view.View;

import android.view.Window;

import android.view.WindowManager;

/**

  • 弹窗控制

*/

public class AlertController {

private AlertDialog mAlertDialog;

private Window mWindow;

private DialogViewHelper mViewHelper;

public AlertController(AlertDialog alertDialog, Window window) {

mAlertDialog = alertDialog;

mWindow = window;

}

public void setDialogViewHelper(DialogViewHelper dialogViewHelper) {

mViewHelper = dialogViewHelper;

}

public void setText(int viewId, CharSequence text) {

mViewHelper.setText(viewId, text);

}

public void setIcon(int viewId, int resId) {

mViewHelper.setIcon(viewId, resId);

}

public T getView(int viewId) {

return mViewHelper.getView(viewId);

}

public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {

mViewHelper.setOnClickListener(viewId, onClickListener);

}

public AlertDialog getDialog() {

return mAlertDialog;

}

public Window getWindow() {

return mWindow;

}

//-------------------------------------------------------------------------------------------------

public static class AlertParams {

public Context mContext;

//对话框主题背景

public int mThemeResId;

public boolean mCancelable;

public DialogInterface.OnCancelListener mOnCancelListener;

public DialogInterface.OnDismissListener mOnDismissListener;

public DialogInterface.OnKeyListener mOnKeyListener;

//文本颜色

public SparseArray mTextColorArray = new SparseArray<>();

//存放文本的更改

public SparseArray mTextArray = new SparseArray<>();

//存放点击事件

public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();

//存放长按点击事件

public SparseArray<View.OnLongClickListener> mLondClickArray = new SparseArray<>();

//存放对话框图标

public SparseArray mIconArray = new SparseArray<>();

//存放对话框图片

public SparseArray mBitmapArray = new SparseArray<>();

//对话框布局资源id

public int mLayoutResId;

//对话框的view

public View mView;

//对话框宽度

public int mWidth;

//对话框高度

public int mHeight;

//对话框垂直外边距

public int mHeightMargin;

//对话框横向外边距

public int mWidthMargin;

//动画

public int mAnimation;

//对话框显示位置

public int mGravity = Gravity.CENTER;

public AlertParams(Context context, int themeResId) {

mContext = context;

mThemeResId = themeResId;

}

public void apply(AlertController alert) {

//设置对话框布局

DialogViewHelper dialogViewHelper = null;

if (mLayoutResId != 0) {

dialogViewHelper = new DialogViewHelper(mContext, mLayoutResId);

}

if (mView != null) {

dialogViewHelper = new DialogViewHelper();

dialogViewHelper.setContentView(mView);

}

if (dialogViewHelper == null) {

throw new IllegalArgumentException(“please set layout”);

}

//将对话框布局设置到对话框

alert.getDialog().setContentView(dialogViewHelper.getContentView());

//设置DialogViewHelper辅助类

alert.setDialogViewHelper(dialogViewHelper);

//设置文本

for (int i = 0; i < mTextArray.size(); i++) {

alert.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));

}

//设置图标

for (int i = 0; i < mIconArray.size(); i++) {

alert.setIcon(mIconArray.keyAt(i), mIconArray.valueAt(i));

}

//设置点击

for (int i = 0; i < mClickArray.size(); i++) {

alert.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));

}

//配置自定义效果,底部弹出,宽高,动画,全屏

Window window = alert.getWindow();

window.setGravity(mGravity);//显示位置

if (mAnimation != 0) {

window.setWindowAnimations(mAnimation);//设置动画

}

//设置宽高

WindowManager.LayoutParams params = window.getAttributes();

params.width = mWidth;

params.height = mHeight;

params.verticalMargin = mHeightMargin;

params.horizontalMargin = mWidthMargin;

window.setAttributes(params);

}

}

}

DialogViewHelper.java

package com.llw.mvplibrary.view.dialog;

import android.content.Context;

import android.util.SparseArray;

import android.view.LayoutInflater;

import android.view.View;

import android.widget.ImageView;

import android.widget.TextView;

import java.lang.ref.WeakReference;

public class DialogViewHelper {

private View mContentView;

private SparseArray<WeakReference> mViews;

public DialogViewHelper(Context context, int layoutResId) {

this();

mContentView = LayoutInflater.from(context).inflate(layoutResId, null);

}

public DialogViewHelper() {

mViews = new SparseArray<>();

}

public void setText(int viewId, CharSequence text) {

TextView tv = getView(viewId);

if (tv != null) {

tv.setText(text);

}

}

public T getView(int viewId) {

WeakReference weakReference = mViews.get(viewId);

View view = null;

if (weakReference != null) {

view = weakReference.get();

}

if (view == null) {

view = mContentView.findViewById(viewId);

if (view != null) {

mViews.put(viewId, new WeakReference<>(view));

}

}

return (T) view;

}

public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {

View view = getView(viewId);

if (view != null) {

view.setOnClickListener(onClickListener);

}

}

public void setIcon(int viewId, int resId) {

ImageView iv = getView(viewId);

if (iv != null) {

iv.setImageResource(resId);

}

}

public void setContentView(View contentView) {

mContentView = contentView;

}

public View getContentView() {

return mContentView;

}

}

OK,弹窗有了,布局还没有的。在app下的layout中新建一个dialog_update_app_tip.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“@dimen/dp_270”

android:layout_height=“wrap_content”

android:orientation=“vertical”>

<ImageView

android:layout_width=“@dimen/dp_270”

android:layout_height=“wrap_content”

android:background=“@mipmap/pic_update” />

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:background=“@color/white”

android:gravity=“center_horizontal”

android:orientation=“vertical”

android:paddingBottom=“@dimen/dp_26”>

<TextView

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginTop=“@dimen/dp_8”

android:text=“发现新版本”

android:textColor=“@color/black”

android:textSize=“@dimen/sp_16” />

<TextView

android:id=“@+id/tv_update_info”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_marginTop=“@dimen/dp_12”

android:paddingLeft=“@dimen/dp_24”

android:paddingRight=“@dimen/dp_24”

android:text=“再也不会错过更多精彩啦!” />

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“@dimen/dp_50”>

<TextView

android:id=“@+id/tv_cancel”

android:layout_width=“@dimen/dp_0”

android:layout_height=“match_parent”

android:layout_weight=“1”

android:background=“@drawable/shape_left_bg”

android:foreground=“@drawable/bg_white”

android:gravity=“center”

最后

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。

当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
v != null) {

iv.setImageResource(resId);

}

}

public void setContentView(View contentView) {

mContentView = contentView;

}

public View getContentView() {

return mContentView;

}

}

OK,弹窗有了,布局还没有的。在app下的layout中新建一个dialog_update_app_tip.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“@dimen/dp_270”

android:layout_height=“wrap_content”

android:orientation=“vertical”>

<ImageView

android:layout_width=“@dimen/dp_270”

android:layout_height=“wrap_content”

android:background=“@mipmap/pic_update” />

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:background=“@color/white”

android:gravity=“center_horizontal”

android:orientation=“vertical”

android:paddingBottom=“@dimen/dp_26”>

<TextView

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginTop=“@dimen/dp_8”

android:text=“发现新版本”

android:textColor=“@color/black”

android:textSize=“@dimen/sp_16” />

<TextView

android:id=“@+id/tv_update_info”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_marginTop=“@dimen/dp_12”

android:paddingLeft=“@dimen/dp_24”

android:paddingRight=“@dimen/dp_24”

android:text=“再也不会错过更多精彩啦!” />

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“@dimen/dp_50”>

<TextView

android:id=“@+id/tv_cancel”

android:layout_width=“@dimen/dp_0”

android:layout_height=“match_parent”

android:layout_weight=“1”

android:background=“@drawable/shape_left_bg”

android:foreground=“@drawable/bg_white”

android:gravity=“center”

最后

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。

[外链图片转存中…(img-kFrM2Xie-1714412175678)]

当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值