企业一般都会为应用提供手机版与 Pad 版的程序,我们可以利用 Android 碎片,编写出兼容手机与平板的应用程序。
因为新闻列表会用到 RecyclerView,所以我们先在 app/build.gradle 中引入依赖库:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
testCompile 'junit:junit:4.12'
}
接下来,编写一个新闻的实体类:
public class News {
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 图片列表
*/
private List<Integer> images;
public News(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<Integer> getImages() {
return images;
}
public void setImages(List<Integer> images) {
this.images = images;
}
@Override
public String toString() {
return "News{" +
"title='" + title + '\'' +
", content='" + content + '\'' +
", images=" + images +
'}';
}
}
新闻的实体类包含标题、内容和图片资源列表。
新建布局 news_content_frag.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:id="@+id/visibility_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible"
>
<!--标题-->
<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:padding="10dp"
android:textSize="26sp"
android:textColor="#000"
android:lineSpacingExtra="3dp"
/>
<!--分割线-->
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#d0d0d0"/>
<ImageView
android:id="@+id/news_image"
android:layout_width="400sp"
android:layout_height="300sp"
android:layout_gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
/>
<!--内容-->
<TextView
android:id="@+id/news_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textSize="18sp"
android:lineSpacingExtra="3dp"
/>
</LinearLayout>
</RelativeLayout>
新闻内容的布局主要分为两个部分,头部显示新闻标题,正文显示新闻图片与内容,中间使用一条细线分隔开。细线采用 View 实现,把 View的宽与高都设置为 1dp,然后再通过 background 设置背景色即可。
然后再新建一个 NewsContentFragment 类,作为新闻内容的碎片:
public class NewsContentFragment extends Fragment {
private View view;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.news_content_frag, container, false);
return view;
}
/**
* 更新
*
* @param title 标题
* @param content 内容
* @param imageResId 图片资源 ID
*/
public void refresh(String title, String content, int imageResId) {
view.findViewById(R.id.visibility_layout).setVisibility(View.VISIBLE);
((TextView) view.findViewById(R.id.news_title)).setText(title);
((ImageView) view.findViewById(R.id.news_image)).setImageResource(imageResId);
((TextView) view.findViewById(R.id.news_content)).setText(content);
}
}
这里提供了一个 refresh 方法,用于将新闻的标题、图片与内容显示在界面中。
这样就把新闻内容的碎片和布局创建好了,但它们都是运行在双页模式中的,所以我们还需创建一个活动 NewsContentActivity,用于单页模式中。
NewsContentActivity 的 布局文件 activity_news_content.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/news_content_fragment"
android:name="net.deniro.android.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"></fragment>
</LinearLayout>
这里充分发挥了代码的复用性,直接在布局中引入了 NewsContentFragment,这相当于把 news_content_frag 布局也自动加载了进来。
然后编写 NewsContentActivity 的代码:
public class NewsContentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news_content);
//获取传入的数据
Intent intent = getIntent();
String title = intent.getStringExtra("title");
String content = intent.getStringExtra("content");
int imageResId = intent.getIntExtra("imageResId", 0);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
((NewsContentFragment) fragment).refresh(title, content, imageResId);
}
/**
* 启动活动
*
* @param context
* @param title 标题
* @param content 内容
* @param imageResId 图片资源 ID
*/
public static void start(Context context, String title, String content, int imageResId) {
Intent intent = new Intent(context, NewsContentActivity.class);
intent.putExtra("title", title);
intent.putExtra("content", content);
intent.putExtra("imageResId", imageResId);
context.startActivity(intent);
}
}
在 onCreate() 方法中,我们通过 Intent 获取传入的新闻标题、图片(这里为了简便,只取出图片列表的第一张图片)和内容,然后调用 FragmentManager 的 findFragmentById() 方法得到 NewsContentFragment 的实例,接着调用它的 refresh() 方法,传入相应的数据。
这里还为 Activity 定义了一个静态的启动方法 start 。
接着,创建显示新闻列表的布局 news_title_frag.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/news_title_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
这里只定义了一个用于显示新闻列表的 RecyclerView。
然后,新建 news_item.xml 作为 RecyclerView 子项的布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<TextView
android:id="@+id/news_title"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:gravity="left"
android:paddingTop="40dp"
android:paddingLeft="15dp"
android:paddingRight="30dp"
android:textSize="20sp"
android:textColor="#000"
>
</TextView>
<ImageView
android:id="@+id/news_image"
android:layout_width="130dp"
android:layout_height="130dp"
android:layout_gravity="right"
android:paddingRight="15dp"
/>
<!--分割线-->
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#d0d0d0"/>
</FrameLayout>
这里的布局采用 FrameLayout,然后使用 layout_gravity 来控制控件在布局中的对齐方式。
因为新闻列表和子项布局都创建好了,所以接下来就需要创建一个 NewsTitleFragment 作为展示新闻列表的碎片:
public class NewsTitleFragment extends Fragment {
private static final String TAG = "NewsTitleFragment";
/**
* 是否为双页模式
*/
private boolean isTwoPage;
class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List<News> newsList;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView titleText;
public ViewHolder(View itemView) {
super(itemView);
titleText = (TextView) itemView.findViewById(R.id.news_title);
image = (ImageView) itemView.findViewById(R.id.news_image);
}
}
public NewsAdapter(List<News> data) {
newsList = data;
Log.d(TAG, "NewsAdapter: " + newsList);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
News news = newsList.get(holder.getAdapterPosition());
Integer firstImageResId = news.getImages().get(0);
if (isTwoPage) {//双页模式,则直接刷新 NewsContentFragment 中的内容
((NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment)).refresh(news.getTitle(), news.getContent(), firstImageResId);
} else {//单页模式,直接启动 NewsContentActivity 活动
NewsContentActivity.start(getActivity(), news.getTitle(), news.getContent(), firstImageResId);
}
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
News news = newsList.get(position);
holder.titleText.setText(news.getTitle());
holder.image.setImageResource(news.getImages().get(0));//取第一张图片
}
@Override
public int getItemCount() {
return newsList.size();
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
//为 RecyclerView 填充数据
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(new NewsAdapter(getNews()));
return view;
}
private List<News> getNews() {
List<News> list = new ArrayList<>();
addNews(list, R.drawable.new1, "在韩国上演的北京8分钟,中国首次用AI展示国家形象", "时隔14年后,奥林匹克再次进入北京时间。 刚刚结束的平昌冬奥会闭幕仪式上,张艺谋和团队用8分钟的时间,展示了国家形象并向全球发出邀请。与上次雅典8分钟不同的是,这次中国的形象有了更多科技元素。可能你不知道,整个展示过程其实是由人工智能和人类演员共同完成。今晚20点20分,2名大熊猫特使、22名北体大学生滑冰登场,紧随人类表演者亮相的还有24面冰雪通透的屏幕,按照长城砖比例设计,由24个机器人操作。整个表演也因此呈现“画中画”模式。");
addNews(list, R.drawable.new2, "AI的乌托邦!谷歌母公司正在建造超级智慧城市 ", "xxx");
addNews(list, R.drawable.new3, "诺基亚CEO:世界各大运营商加速 5G部署将提前一年 ", "xxx");
addNews(list, R.drawable.new4, "买房难,苹果新总部附近房屋平均售价116万美元 ", "xxx");
addNews(list, R.drawable.new5, "人民日报三问区块链:区分是技术创新还是集资创新 ", "xxx");
Log.d(TAG, "getNews: " + list);
return list;
}
/**
* 新增新闻
*
* @param list
* @param image 图片资源
* @param title 标题
* @param content 内容
*/
private void addNews(List<News> list, int image, String title, String content) {
News news = new News(title, content);
List<Integer> images = new ArrayList<>();
images.add(image);
news.setImages(images);
list.add(news);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {//双页模式
isTwoPage = true;
} else {
isTwoPage = false;
}
}
}
这里新建了一个内部类 NewsAdapter 来作为 RecyclerView 的适配器,因为内部类可以直接访问外部类的变量。
在 onCreateViewHolder 中,我们注册了点击事件,并根据当前所处的模式,来进行相应的逻辑处理。如果是单页模式,就启动一个新的活动来显示新闻内容;如果是双页模式,就更新新闻内容碎片中的内容。
在 onCreateView 方法中,把数据填充到 RecyclerView 中。
在 onActivityCreated 方法中,我们通过能否找到一个 news_content_layout 的 View 来判断当前处于的模式(双页或单页)。这是通过限定符来实现的:
主布局 activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/news_title_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="net.deniro.android.fragmentbestpractice.NewsTitleFragment"
/>
</FrameLayout>
接着,在 res 目录下新建 layout-sw600dp 文件夹,并在这个文件夹下再新建一个布局
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="@+id/news_title_fragment"
android:name="net.deniro.android.fragmentbestpractice.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/news_content_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
>
<fragment
android:id="@+id/news_content_fragment"
android:name="net.deniro.android.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
这样当运行在屏幕宽度大于 600 dp 的设备上时,就会加载 news_content_fragment 布局,这样我们也就能判断出当前活动所处的模式啦O(∩_∩)O~
在手机模拟器中运行程序:
点击一条新闻:
然后在平板模拟器中运行程序:
是不是很酷呀O(∩_∩)O~