说说 Android 中如何实现同时兼容手机与平板的新闻应用界面
企业一般都会为应用提供手机版与 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 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 getImages() { return images; } public void setImages(List 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 { private List 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 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 getNews() { List 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 list, int image, String title, String content) { News news = new News(title, content); List 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~
更多相关文章
- 第七章 布局
- Android的启动模式
- Android 5.0上动态布局层级覆盖问题
- Android自适应不同分辨率或不同屏幕大小的layout布局(横屏|竖屏)
- android常用的设计模式总结 一
- Android 中 Activity 的4种启动模式
- 安卓课程六 android常用布局属性的介绍