Android MVVM框架搭建(五)Navigation + Fragment + BottomNavigationView

.fallback(R.drawable.wallpaper_bg) //url为空的时候,显示的图片

.error(R.mipmap.ic_loading_failed);//图片加载失败后,显示的图片

将这个值配置进去,如下图所示:

在这里插入图片描述

下面我们运行一下看是什么效果。

在这里插入图片描述

效果还可以的,下面进入主页面的代码编写。

五、主页面


当到了每日壁纸页面时,我们需要再提供一个入口可以进入下一个页面,现在的每日壁纸页面不能算是真正意义上的主页面,因此我们写一个入口,可以在MainActivity中增加一个浮动按钮,页面上下滑动时控制按钮的显示和消失。下面在activity_main.xml中增加如下布局代码:

<com.google.android.material.floatingactionbutton.FloatingActionButton

android:id=“@+id/fab_home”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“end|bottom”

android:layout_margin=“20dp”

android:background=“@color/purple_500”

android:onClick=“toHome”

android:src=“@mipmap/ic_home”

app:backgroundTint=“@color/purple_500”

app:fabSize=“auto”

tools:ignore=“UsingOnClickInXml”

android:contentDescription=“主页” />

添加的位置如下,这里的图标到我的源码里拿就好,白色的放出来也看不见。

在这里插入图片描述

下面回到MainActivity中,继承BaseActivity,在initView方法中增加如下代码:

//页面上下滑动监听

dataBinding.scrollView.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {

if (scrollY > oldScrollY) {

//上滑

dataBinding.fabHome.hide();

} else {

//下滑

dataBinding.fabHome.show();

}

});

然后也增加一个方法,当点击时跳转到HomeActivity,我们将在这个HomeActivity中显示Fragment,现在还没有,下面会创建的。

public void toHome(View view) {

jumpActivity(HomeActivity.class);

}

代码添加位置如下图所示:

在这里插入图片描述

下面在activity包下创建一个HomeActivity,对应的布局是activity_home.xml,在改动之前我们先做好准备的工作。

六、Navigation使用


在res包下新建一个navigation包,包下新建一个nav_graph.xml,里面的代码如下:

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

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

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”

android:id=“@+id/nav_graph”

app:startDestination=“@id/news_fragment”>

<fragment

android:id=“@+id/news_fragment”

android:name=“com.llw.mvvm.ui.fragment.NewsFragment”

android:label=“news_fragment”

tools:layout=“@layout/news_fragment” />

<fragment

android:id=“@+id/video_fragment”

android:name=“com.llw.mvvm.ui.fragment.VideoFragment”

android:label=“video_fragment”

tools:layout=“@layout/video_fragment” />

这里就是将Fragment配置到Navigation中,app:startDestination表示显示的第一个Fragment。那么这一步就完成了,下面是另一个操作,就是通过点击底部导航栏菜单去进行Fragment的切换。我们在res下新建一个menu包,包下新建一个navigation_menu.xml,里面的代码如下:

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

<item

android:id=“@+id/news_fragment”

android:icon=“@mipmap/ic_hot_news”

android:title=“新闻” />

<item

android:id=“@+id/video_fragment”

android:icon=“@mipmap/ic_hot_video”

android:title=“视频” />

这里有两个图标,同样是白色的。

在这里插入图片描述

下面我们回到activity_home.xml中,里面的代码如下:

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

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

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”>

<RelativeLayout

android:layout_width=“match_parent”

android:layout_height=“match_parent”

tools:context=“.ui.activity.HomeActivity”>

<com.google.android.material.appbar.MaterialToolbar

android:id=“@+id/toolbar”

android:layout_width=“match_parent”

android:layout_height=“?attr/actionBarSize”

android:background=“@color/purple_500”>

<TextView

android:id=“@+id/tv_title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center”

android:text=“头条新闻”

android:textColor=“@color/white”

android:textSize=“18sp”

android:textStyle=“bold” />

</com.google.android.material.appbar.MaterialToolbar>

<fragment

android:id=“@+id/nav_host_fragment”

android:name=“androidx.navigation.fragment.NavHostFragment”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:layout_above=“@+id/bottom_navigation”

android:layout_below=“@+id/toolbar”

app:navGraph=“@navigation/nav_graph” />

<com.google.android.material.bottomnavigation.BottomNavigationView

android:id=“@+id/bottom_navigation”

android:layout_width=“match_parent”

android:layout_height=“?attr/actionBarSize”

android:layout_alignParentBottom=“true”

android:background=“#FFF”

app:menu=“@menu/navigation_menu” />

这里分为三个部分,一个是标题栏、一个是装载Fragment的容器,另一个是控制Fragment切换的。

下面我们进入到HomeActivity页面,修改代码如下:

public class HomeActivity extends BaseActivity {

private ActivityHomeBinding binding;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

binding = DataBindingUtil.setContentView(this, R.layout.activity_home);

initView();

}

/**

  • 初始化

*/

private void initView() {

//获取navController

NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

binding.bottomNavigation.setOnNavigationItemSelectedListener(item -> {

switch (item.getItemId()) {

case R.id.news_fragment:

binding.tvTitle.setText(“头条新闻”);

navController.navigate(R.id.news_fragment);

break;

case R.id.video_fragment:

binding.tvTitle.setText(“热门视频”);

navController.navigate(R.id.video_fragment);

break;

default:

}

return true;

});

}

}

下面在fragment包下创建一个BaseFragment,里面的代码如下:

public class BaseFragment extends Fragment {

protected AppCompatActivity context;

@Override

public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

return super.onCreateView(inflater, container, savedInstanceState);

}

@Override

public void onViewCreated(@NonNull @NotNull View view, @Nullable Bundle savedInstanceState) {

super.onViewCreated(view, savedInstanceState);

}

@Override

public void onAttach(@NonNull @NotNull Context context) {

super.onAttach(context);

if(context instanceof AppCompatActivity){

this.context = (AppCompatActivity) context;

}

}

@Override

public void onDetach() {

super.onDetach();

context = null;

}

protected void showMsg(String msg) {

Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();

}

}

然后修改NewsFragment中的代码:

public class NewsFragment extends BaseFragment {

private NewsFragmentBinding binding;

public static NewsFragment newInstance() {

return new NewsFragment();

}

@Override

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,

@Nullable Bundle savedInstanceState) {

binding = DataBindingUtil.inflate(inflater, R.layout.news_fragment, container, false);

return binding.getRoot();

}

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

super.onActivityCreated(savedInstanceState);

NewsViewModel mViewModel = new ViewModelProvider(this).get(NewsViewModel.class);

}

}

再修改VideoFragment的代码:

public class VideoFragment extends BaseFragment {

private VideoFragmentBinding binding;

public static VideoFragment newInstance() {

return new VideoFragment();

}

@Override

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,

@Nullable Bundle savedInstanceState) {

binding = DataBindingUtil.inflate(inflater, R.layout.video_fragment, container, false);

return binding.getRoot();

}

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

super.onActivityCreated(savedInstanceState);

VideoViewModel mViewModel = new ViewModelProvider(this).get(VideoViewModel.class);

}

}

这里我们在点击底部导航栏时切换Fragment并且更改一下标题栏的文字,下面运行一下。

在这里插入图片描述

详细的使用说明可以看看这篇文章:Android Navigation + Fragment 制作APP主页面导航(步骤 + 源码),看完后你了解的也许会更多。

七、聚合数据请求


这里我们使用聚合的API数据,聚合API,点击进入完成注册登录,然后可以申请数据API。

在这里插入图片描述

在这里插入图片描述

申请免费的API,每天有一百次请求,因此我们可以把数据请求一次之后保存到本地数据库中。

① NetworkApi

这两个API的接口是不同的地址,修改一下NetworkApi中的setUrlType方法。

private static void setUrlType(int type) {

switch (type) {

case 0:

//必应

BASE_URL = “https://cn.bing.com”;

break;

case 1:

//热门壁纸

BASE_URL = “http://service.picasso.adesk.com”;

break;

case 2:

//聚合API 1

BASE_URL = “http://v.juhe.cn”;

break;

case 3:

//聚合API 2

BASE_URL = “http://apis.juhe.cn”;

break;

default:

break;

}

}

这里两个接口分别是用于请求新闻数据和视频数据的。

② ApiService

在ApiService中增加两个接口,代码如下所示:

/**

  • 聚合新闻数据

*/

@GET(“/toutiao/index?type=&page=&page_size=&is_filter=&key=99d3951ed32af2930afd9b38293a08a2”)

Observable news();

/**

  • 聚合热门视频数据

*/

@GET(“/fapig/douyin/billboard?type=hot_video&size=20&key=a9c49939cae34fc7dae570b1a4824be4”)

Observable video();

针对这个情况我们同样需要对数据库进行一次升级,这一次我们增加两个表。

③ 数据库升级

首先在bean包下新建两个实体,News和Video。里面的内容都是我根据接口返回的数据制作的,News里的代码如下:

@Entity(tableName = “news”)

public class News {

@PrimaryKey(autoGenerate = true)

private int uid;

private String uniquekey;

private String title;

private String date;

private String category;

private String author_name;

private String url;

private String thumbnail_pic_s;

private String is_content;

public int getUid() {

return uid;

}

public void setUid(int uid) {

this.uid = uid;

}

public String getUniquekey() {

return uniquekey;

}

public void setUniquekey(String uniquekey) {

this.uniquekey = uniquekey;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getDate() {

return date;

}

public void setDate(String date) {

this.date = date;

}

public String getCategory() {

return category;

}

public void setCategory(String category) {

this.category = category;

}

public String getAuthor_name() {

return author_name;

}

public void setAuthor_name(String author_name) {

this.author_name = author_name;

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

public String getThumbnail_pic_s() {

return thumbnail_pic_s;

}

public void setThumbnail_pic_s(String thumbnail_pic_s) {

this.thumbnail_pic_s = thumbnail_pic_s;

}

public String getIs_content() {

return is_content;

}

public void setIs_content(String is_content) {

this.is_content = is_content;

}

public News() {}

@Ignore

public News(String uniquekey, String title, String date, String category, String author_name, String url, String thumbnail_pic_s, String is_content) {

this.uniquekey = uniquekey;

this.title = title;

this.date = date;

this.category = category;

this.author_name = author_name;

this.url = url;

this.thumbnail_pic_s = thumbnail_pic_s;

this.is_content = is_content;

}

}

Video的代码如下:

@Entity(tableName = “video”)

public class Video {

@PrimaryKey(autoGenerate = true)

private int uid;

private String title;

private String share_url;

private String author;

private String item_cover;

private String hot_words;

public int getUid() {

return uid;

}

public void setUid(int uid) {

this.uid = uid;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getShare_url() {

return share_url;

}

public void setShare_url(String share_url) {

this.share_url = share_url;

}

public String getAuthor() {

return author;

}

public void setAuthor(String author) {

this.author = author;

}

public String getItem_cover() {

return item_cover;

}

public void setItem_cover(String item_cover) {

this.item_cover = item_cover;

}

public String getHot_words() {

return hot_words;

}

public void setHot_words(String hot_words) {

this.hot_words = hot_words;

}

@Ignore

public Video(String title, String share_url, String author, String item_cover, String hot_words) {

this.title = title;

this.share_url = share_url;

this.author = author;

this.item_cover = item_cover;

this.hot_words = hot_words;

}

public Video() {}

}

然后是Dao类,在dao包下新建一个NewsDao和VideoDao的接口,NewsDao代码如下:

@Dao

public interface NewsDao {

@Query(“SELECT * FROM news”)

Flowable<List> getAll();

@Insert(onConflict = OnConflictStrategy.REPLACE)

Completable insertAll(List news);

@Query(“DELETE FROM news”)

Completable deleteAll();

}

VideoDao代码如下:

@Dao

public interface VideoDao {

@Query(“SELECT * FROM video”)

Flowable<List> getAll();

@Insert(onConflict = OnConflictStrategy.REPLACE)

Completable insertAll(List videos);

@Query(“DELETE FROM video”)

Completable deleteAll();

}

最后我们进入AppDatabase中,对数据库进行升级迁移,在AppDatabase中新增如下代码:

/**

  • 版本升级迁移到3 新增新闻表和视频表

*/

static final Migration MIGRATION_2_3 = new Migration(2, 3) {

@Override

public void migrate(SupportSQLiteDatabase database) {

//创建新闻表

database.execSQL("CREATE TABLE news " +

"(uid INTEGER NOT NULL, " +

"uniquekey TEXT, " +

"title TEXT, " +

“date TEXT,” +

“category TEXT,” +

“author_name TEXT,” +

“url TEXT,” +

“thumbnail_pic_s TEXT,” +

“is_content TEXT,” +

“PRIMARY KEY(uid))”);

//创建视频表

database.execSQL("CREATE TABLE video " +

"(uid INTEGER NOT NULL, " +

“title TEXT,” +

“share_url TEXT,” +

“author TEXT,” +

“item_cover TEXT,” +

“hot_words TEXT,” +

“PRIMARY KEY(uid))”);

}

};

然后再增加两个抽象方法,就是之前的两个数据操作类,我们这样写了之后通过Room的编译时技术会对这两个抽象类中的接口进行一个实现,不需要我们去管它。

public abstract NewsDao newsDao();

public abstract VideoDao videoDao();

下面就是对数据库进行升级了,如下图所示:

在这里插入图片描述

注意我标注的地方,少一个都会出现升级不成功,或者你直接都编译不成功或者程序运行闪退的情况。

现在我们的数据库有了,接下来要做的就是数据的或者和保存了。

④ 数据存储库

下面就是Repository了,我们在repository包下新建NewsRspository和VideoRepository两个类,然后为了方便管理数据的请求方式,我们同样需要在Constant中增加几个常量来保存当天是否有请求网络接口数据,在Constant中增加如下代码:

/**

  • 今日是否请求了聚合新闻数据

*/

public static final String IS_TODAY_REQUEST_NEWS = “isTodayRequestNews”;

/**

  • 今日请求聚合新闻数据的时间戳

*/

public static final String REQUEST_TIMESTAMP_NEWS = “newsRequestTimestamp”;

/**

  • 今日是否请求了聚合视频数据

*/

public static final String IS_TODAY_REQUEST_VIDEO = “isTodayRequestVideo”;

/**

  • 今日请求聚合视频数据的时间戳

*/

public static final String REQUEST_TIMESTAMP_VIDEO = “videoRequestTimestamp”;

然后我们再来编辑NewsRepository的代码:

@SuppressLint(“CheckResult”)

public class NewsRepository {

private static final String TAG = NewsRepository.class.getSimpleName();

final MutableLiveData news = new MutableLiveData<>();

public final MutableLiveData failed = new MutableLiveData<>();

/**

  • 获取新闻数据

  • @return news

*/

public MutableLiveData getNews() {

//今日此接口是否已经请求

if (MVUtils.getBoolean(Constant.IS_TODAY_REQUEST_NEWS)) {

if (DateUtil.getTimestamp() <= MVUtils.getLong(Constant.REQUEST_TIMESTAMP_NEWS)) {

getNewsForLocalDB();

} else {

getNewsForNetwork();

}

} else {

getNewsForNetwork();

}

return news;

}

/**

  • 从本地数据库获取新闻

*/

private void getNewsForLocalDB() {

Log.d(TAG, “getNewsForLocalDB: 从本地数据库获取 新闻数据”);

NewsResponse newsResponse = new NewsResponse();

NewsResponse.ResultBean resultBean = new NewsResponse.ResultBean();

List<NewsResponse.ResultBean.DataBean> dataBeanList = new ArrayList<>();

Flowable<List> listFlowable = BaseApplication.getDb().newsDao().getAll();

CustomDisposable.addDisposable(listFlowable, newss -> {

for (News news1 : newss) {

NewsResponse.ResultBean.DataBean dataBean = new NewsResponse.ResultBean.DataBean();

dataBean.setUniquekey(news1.getUniquekey());

dataBean.setTitle(news1.getTitle());

dataBean.setDate(news1.getDate());

dataBean.setAuthor_name(news1.getAuthor_name());

dataBean.setCategory(news1.getCategory());

dataBean.setThumbnail_pic_s(news1.getThumbnail_pic_s());

dataBean.setIs_content(news1.getIs_content());

dataBeanList.add(dataBean);

}

resultBean.setData(dataBeanList);

newsResponse.setResult(resultBean);

news.postValue(newsResponse);

});

}

/**

  • 从网络获取壁纸数据

*/

private void getNewsForNetwork() {

Log.d(TAG, “getNewsForNetwork: 从网络获取 热门壁纸”);

NetworkApi.createService(ApiService.class, 2).

news().compose(NetworkApi.applySchedulers(new BaseObserver() {

@Override

public void onSuccess(NewsResponse newsResponse) {

if (newsResponse.getError_code() == 0) {

//保存本地数据

saveNews(newsResponse);

news.setValue(newsResponse);

} else {

failed.postValue(newsResponse.getReason());

}

}

@Override

public void onFailure(Throwable e) {

failed.postValue("News Error: " + e.toString());

}

}));

}

/**

  • 保存热门壁纸数据

*/

private void saveNews(NewsResponse newsResponse) {

MVUtils.put(Constant.IS_TODAY_REQUEST_NEWS, true);

MVUtils.put(Constant.REQUEST_TIMESTAMP_NEWS, DateUtil.getMillisNextEarlyMorning());

Completable deleteAll = BaseApplication.getDb().newsDao().deleteAll();

CustomDisposable.addDisposable(deleteAll, () -> {

Log.d(TAG, “saveNews: 删除数据成功”);

List newsList = new ArrayList<>();

for (NewsResponse.ResultBean.DataBean dataBean : newsResponse.getResult().getData()) {

newsList.add(new News(dataBean.getUniquekey(),dataBean.getTitle(),dataBean.getDate(),dataBean.getCategory(),

dataBean.getAuthor_name(),dataBean.getUrl(),dataBean.getThumbnail_pic_s(),dataBean.getIs_content()));

}

//保存到数据库

Completable insertAll = BaseApplication.getDb().newsDao().insertAll(newsList);

Log.d(TAG, “saveNews: 插入数据:” + newsList.size() + “条”);

//RxJava处理Room数据存储

CustomDisposable.addDisposable(insertAll, () -> Log.d(TAG, “saveNews: 新闻数据保存成功”));

});

}

}

这里的代码和之前WallPaperRepository中的代码神似,逻辑上基本一致,只不过是不同的接口和不同的数据表,同事我在当前的这个Repository中增加了一个异常信息的LiveData,因为请求接口你可能会需要很多情况,最理想的时能获取到数据,但是也有其他情况,例如接口地址错误访问不到、请求返回的数据为空,请求次数达到上限等一些异常。因此我们有必要做一个异常信息的处理,然后传递到ViewModel中,最终在Activity中对这个异常进行观察,及时通知到页面上。不然我光打印日志,用户是看不到的。

VideoRepository的代码如下:

@SuppressLint(“CheckResult”)

public class VideoRepository {

public static final String TAG = VideoRepository.class.getSimpleName();

final MutableLiveData video = new MutableLiveData<>();

public final MutableLiveData failed = new MutableLiveData<>();

/**

  • 获取视频数据

  • @return video

*/

public MutableLiveData getVideo() {

//今日此接口是否已经请求

if (MVUtils.getBoolean(Constant.IS_TODAY_REQUEST_VIDEO)) {

if (DateUtil.getTimestamp() <= MVUtils.getLong(Constant.REQUEST_TIMESTAMP_VIDEO)) {

getVideoForLocalDB();

} else {

getVideoForNetwork();

}

} else {

getVideoForNetwork();

}

return video;

}

/**

  • 从本地数据库获取新闻

*/

private void getVideoForLocalDB() {

Log.d(TAG, “getVideoForLocalDB: 从本地数据库获取 视频数据”);

VideoResponse videoResponse = new VideoResponse();

List<VideoResponse.ResultBean> dataBeanList = new ArrayList<>();

Flowable<List> listFlowable = BaseApplication.getDb().videoDao().getAll();

CustomDisposable.addDisposable(listFlowable, videos -> {

for (Video video : videos) {

VideoResponse.ResultBean resultBean = new VideoResponse.ResultBean();

resultBean.setTitle(video.getTitle());

resultBean.setShare_url(video.getShare_url());

resultBean.setAuthor(video.getAuthor());

resultBean.setHot_words(video.getHot_words());

resultBean.setItem_cover(video.getItem_cover());

dataBeanList.add(resultBean);

}

videoResponse.setResult(dataBeanList);

video.postValue(videoResponse);

});

}

/**

  • 从网络获取壁纸数据

*/

private void getVideoForNetwork() {

Log.d(TAG, “getVideoForNetwork: 从网络获取 热门壁纸”);

NetworkApi.createService(ApiService.class, 3)

.video().compose(NetworkApi.applySchedulers(new BaseObserver() {

@Override

public void onSuccess(VideoResponse videoResponse) {

if (videoResponse.getError_code() == 0) {

//保存本地数据

saveVideo(videoResponse);

video.postValue(videoResponse);

} else {

failed.postValue(videoResponse.getReason());

}

}

@Override

public void onFailure(Throwable e) {

failed.postValue("Video Error: " + e.toString());

}

}));

}

/**

  • 保存热门壁纸数据

*/

private void saveVideo(VideoResponse videoResponse) {

MVUtils.put(Constant.IS_TODAY_REQUEST_VIDEO, true);

MVUtils.put(Constant.REQUEST_TIMESTAMP_VIDEO, DateUtil.getMillisNextEarlyMorning());

Completable deleteAll = BaseApplication.getDb().videoDao().deleteAll();

CustomDisposable.addDisposable(deleteAll, () -> {

Log.d(TAG, “saveVideo: 删除数据成功”);

List videoList = new ArrayList<>();

for (VideoResponse.ResultBean resultBean : videoResponse.getResult()) {

videoList.add(new Video(resultBean.getTitle(),resultBean.getShare_url(),resultBean.getAuthor(),

resultBean.getItem_cover(), resultBean.getHot_words()));

}

//保存到数据库

Completable insertAll = BaseApplication.getDb().videoDao().insertAll(videoList);

Log.d(TAG, “saveVideo: 插入数据:” + videoList.size() + “条”);

//RxJava处理Room数据存储

CustomDisposable.addDisposable(insertAll, () -> Log.d(TAG, “saveVideo: 视频数据保存成功”));

});

}

}

这里面的代码也是类似的。如果我们每一个ViewModel中都要有一个failed,那么我们可以定义一个基础ViewModel,然后所有的ViewModel去继承它,这样就会更好一些。

⑤ BaseViewModel

在viewmodels包下新建一个BaseViewModel,里面的代码如下:

public class BaseViewModel extends ViewModel {

public LiveData failed;

}

哦豁,就这么点代码吗?是的,目前就这些,可以根据实际的需求后面再进行添加,不着急。下面我们修改一下NewsViewModel中的代码,如下所示:

public class NewsViewModel extends BaseViewModel {

public LiveData news;

public void getNews() {

NewsRepository newsRepository = new NewsRepository();

failed = newsRepository.failed;

news = newsRepository.getNews();

}

}

然后再修改一下VideoViewModel的代码:

public class VideoViewModel extends BaseViewModel {

public LiveData video;

public void getVideo() {

VideoRepository videoRepository = new VideoRepository();

failed = videoRepository.failed;

video = videoRepository.getVideo();

}

}

另外其他的ViewModel也这样修改一下,我就不重复贴代码了,不了解的看源码对着改一下就好。

八、新闻、视频数据显示


前面做了这么多都是做准备工作,最重要的是要显示数据在Fragment上,下面我们写两个适配器,还有两个xml文件。

下面先创建xml文件,在layout下新建item_news.xml文件,里面的代码如下:

① item布局

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

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

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”>

<variable

name=“news”

type=“com.llw.mvvm.model.NewsResponse.ResultBean.DataBean” />

<RelativeLayout

android:background=“@color/white”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:paddingStart=“12dp”

android:paddingTop=“12dp”

android:paddingEnd=“12dp”>

<TextView

android:id=“@+id/tv_title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentStart=“true”

android:layout_toStartOf=“@id/image”

android:text=“@{news.title}”

android:textColor=“@color/black”

android:textSize=“14sp” />

<com.llw.mvvm.view.CustomImageView

android:id=“@+id/image”

android:layout_marginStart=“12dp”

networkUrl=“@{news.thumbnail_pic_s}”

android:layout_width=“140dp”

android:layout_height=“80dp”

android:layout_alignParentEnd=“true”

android:scaleType=“centerCrop”

app:shapeAppearanceOverlay=“@style/roundedImageStyle_6” />

<TextView

android:id=“@+id/tv_author”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_above=“@+id/tv_date”

android:layout_below=“@+id/tv_title”

android:layout_marginTop=“4dp”

android:text=“@{news.author_name}”

android:textSize=“12sp”

tools:ignore=“NestedWeights” />

<TextView

android:id=“@+id/tv_date”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignBottom=“@id/image”

android:text=“@{news.date}”

android:textSize=“12sp” />

<View

android:layout_width=“match_parent”

android:layout_height=“0.5dp”

android:layout_below=“@id/image”

android:layout_marginTop=“12dp”

android:background=“@color/line” />

这里用的颜色值line,是#EEEEEE,自行在colors.xml中添加就好了。

然后在layout下创建一个item_video.xml文件,里面的代码如下:

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

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

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”>

<variable

name=“video”

type=“com.llw.mvvm.model.VideoResponse.ResultBean” />

<RelativeLayout

android:background=“@color/white”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:paddingStart=“12dp”

android:paddingTop=“12dp”

android:paddingEnd=“12dp”>

<TextView

android:id=“@+id/tv_title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignParentEnd=“true”

android:layout_toEndOf=“@id/image”

android:ellipsize=“end”

android:maxLines=“2”

android:text=“@{video.title}”

android:textColor=“@color/black”

android:textSize=“14sp” />

<com.llw.mvvm.view.CustomImageView

android:id=“@+id/image”

networkUrl=“@{video.item_cover}”

android:layout_width=“140dp”

android:layout_height=“80dp”

android:layout_marginEnd=“12dp”

android:scaleType=“centerCrop”

app:shapeAppearanceOverlay=“@style/roundedImageStyle_6” />

<ImageView

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignStart=“@+id/image”

android:layout_alignTop=“@+id/image”

android:layout_alignEnd=“@id/image”

android:layout_alignBottom=“@+id/image”

android:padding=“20dp”

android:src=“@mipmap/ic_play” />

<TextView

android:id=“@+id/tv_author”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_above=“@+id/tv_words”

android:layout_below=“@+id/tv_title”

android:layout_alignParentEnd=“true”

android:layout_marginTop=“4dp”

android:layout_toEndOf=“@id/image”

android:text=“@{video.author}”

android:textSize=“12sp”

tools:ignore=“NestedWeights” />

<TextView

android:id=“@+id/tv_words”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignBottom=“@id/image”

android:layout_alignParentEnd=“true”

android:layout_toEndOf=“@id/image”

android:ellipsize=“end”

android:maxLines=“1”

android:text=“@{video.hot_words}”

android:textSize=“12sp” />

<View

android:layout_width=“match_parent”

android:layout_height=“0.5dp”

android:layout_below=“@id/image”

android:layout_marginTop=“12dp”

android:background=“@color/line” />

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

【Android高级架构视频学习资源】

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

=“@mipmap/ic_play” />

<TextView

android:id=“@+id/tv_author”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_above=“@+id/tv_words”

android:layout_below=“@+id/tv_title”

android:layout_alignParentEnd=“true”

android:layout_marginTop=“4dp”

android:layout_toEndOf=“@id/image”

android:text=“@{video.author}”

android:textSize=“12sp”

tools:ignore=“NestedWeights” />

<TextView

android:id=“@+id/tv_words”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignBottom=“@id/image”

android:layout_alignParentEnd=“true”

android:layout_toEndOf=“@id/image”

android:ellipsize=“end”

android:maxLines=“1”

android:text=“@{video.hot_words}”

android:textSize=“12sp” />

<View

android:layout_width=“match_parent”

android:layout_height=“0.5dp”

android:layout_below=“@id/image”

android:layout_marginTop=“12dp”

android:background=“@color/line” />

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-elxvARLp-1713720678366)]

[外链图片转存中…(img-b4clXpvi-1713720678368)]

[外链图片转存中…(img-Cz47NLPb-1713720678369)]

[外链图片转存中…(img-epGc4pNF-1713720678370)]

[外链图片转存中…(img-K83hSl8i-1713720678372)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-E44w2Wyw-1713720678373)]

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-IvUUxGI6-1713720678374)]

【Android高级架构视频学习资源】

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值