实践课程

08-实践课程-新闻应用开发介绍和项目搭建

我们来把前面的新闻app把它实现出来。

这里我们又需要用到RecyclerView控件了。

在基础课程中,我们知道它非常适合用在列表的应用中。

我们搭建一个新的项目

叫foxdevelopNews

foxdevelop新闻APP

记得 要用到RecyclerView 需要在gradle中添加依赖库

添加一个新闻实体类

package com.foxdevelop.www.foxdevelopnews;

public class News {
    private  String title;
    private String content;
    public String getTitle()
    {
        return title;
    }
    public String getContent()
    {
        return content;
    }
    public void setTitle(String title)
    {
        this.title = title;
    }

    public  void setContent(String content)
    {
        this.content = content;
    }
}Code language: JavaScript (javascript)

09-右侧新闻详情碎片布局视图

横线我们可以使用view控件 然后给它一个背景颜色就可以了。

添加一个布局

news_detail_fragment.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/detail_linear"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:visibility="invisible"
            >
                <TextView
                    android:id="@+id/new_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:padding="10dp"
                    android:textSize="18dp"
                    />
                <view
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#ccc"/>
            <TextView
                android:id="@+id/new_content"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center"
                android:padding="15dp"
                android:textSize="15dp"
                />
        </LinearLayout>
    <view
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_width="1dp"
        android:background="#ccc"/>
</RelativeLayout>Code language: HTML, XML (xml)

10-右侧碎片类的创建

从扩展包的fragment里面继承

public class NewsDetailFragment extends Fragment {

    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_detail_fragment,container,false);
        return view;
    }

}Code language: PHP (php)

11-手机单页新闻详情活动的创建

手机新闻详情单页活动,我们可以利用平板的新闻详情碎片活动视图的。

因为他们基本是一样的。/

也就是说 平板的新闻详情是碎片 可以用在手机型问详情中

只不过平板的 左侧是列表 手机没有列表

我们添加一个活动类NewsDetailActivity

然后对应的视图使用碎片 然后碎片只有一个

因为它是手机的 所以只有新闻详情的碎片

activity_news_detail 这是活动对应的视图

我们在视图里面添加一个fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/new_content_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.foxdevelop.www.foxdevelopnews.NewsDetailFragment"/>
</LinearLayout>Code language: HTML, XML (xml)

12-活动里面修改碎片视图中的控件内容

比如我们要修改新闻的标题和新闻内容

基础课程中 我们说过 怎么样从一个活动传递数据到另外一个活动

Intent这个东西

当出现红色错误的时候 按alt+enter 可以智能修复

在活动中 我们获取intent中传过来的值 然后调用碎片的方法 给碎片的控件赋值

package com.foxdevelop.www.foxdevelopnews;


import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class NewsDetailActivity extends AppCompatActivity {
    public static  void actionStart(Context context, String newTitle, String newContent)
    {
        Intent intent = new Intent(context,NewsDetailActivity.class);
        intent.putExtra("news_title",newTitle);
        intent.putExtra("news_content",newContent);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_news_detail);

        //从Intent中获取数据
        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");

        //活动获取碎片 给碎片的控件赋值
        NewsDetailFragment newsDetailFragment = (NewsDetailFragment)
                getSupportFragmentManager().findFragmentById(R.id.new_content_fragment);
        newsDetailFragment.SetNews(newsTitle,newsContent);//设置碎片上面的控件的值。
    }
}Code language: JavaScript (javascript)

13-碎片中添加方法给视图赋值

在碎片中 我们添加一个方法 给碎片对于的视图赋值

记得我们前面添加新闻详情碎片视图的时候

就是再活动里面调用碎片的方法给视图的控件赋值。

<?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/detail_linear"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:visibility="invisible"
            >
                <TextView
                    android:id="@+id/new_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:padding="10dp"
                    android:textSize="18dp"
                    />
                <view
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#ccc"/>
            <TextView
                android:id="@+id/new_content"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center"
                android:padding="15dp"
                android:textSize="15dp"
                />
        </LinearLayout>
    <view
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_width="1dp"
        android:background="#ccc"/>
</RelativeLayout>Code language: HTML, XML (xml)
public class NewsDetailFragment extends Fragment {

    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_detail_fragment,container,false);
        return view;
    }

    //碎片中的方法用来给视图控件赋值的
    public void SetNews(String newsTitle, String newsContent) {
        View v = view.findViewById(R.id.detail_linear);
        v.setVisibility(View.VISIBLE);//设为可见
        //获取碎片视图里面的两个TextView 然后赋值
        TextView textViewTitle = (TextView) v.findViewById(R.id.new_title);
        textViewTitle.setText(newsTitle);
        TextView textViewContent = (TextView) v.findViewById(R.id.new_content);
        textViewContent.setText(newsContent);
    }
}Code language: PHP (php)

14-新闻列表碎片视图

内容页可以公用 其实列表部分 页可以共用的。

大家想想,我们的这个平板和手机 是不是很多地方都可以共用的

添加一个视图 名称是news_list_fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/new_list_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
</LinearLayout>Code language: HTML, XML (xml)

添加recylerview的子项布局

news_list_item

<?xml version="1.0" encoding="utf-8"?>
    <TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/news_list_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="end"
        android:textSize="18dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
    />Code language: HTML, XML (xml)

15-新闻列表碎片类

添加一个新闻列表的碎片类NewsListFragment,从Fragment中继承。

package com.foxdevelop.www.foxdevelopnews;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class NewsListFragment extends Fragment {
    private  boolean isPhonePane;

    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_list_fragment,container,false);
        return view;
    }

    //再活动创建的时候 触发的碎片事件 然后再这里 我们判断是手机还是平板isPhonePane是保存状态的
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //if (getActivity().findViewById(R.id.)) {
        //
        //}
    }
}Code language: JavaScript (javascript)

16-手机版本主活动视图

我们提供两套不同的主活动视图

一套是手机版本的 一套是平板版本的

借用前面的限定符 我们可以 让系统自动选择使用那个视图

我们首先添加手机版本的视图

手机版本的视图 我们只需要一个单页就行 所以我们只有一个碎片UI

同时 我们使用帧布局就可以了

添加一个drawable-xhdpi文件夹

然后赋值一份

手机版本的 使用layout下面的activity_main.xml这个视图

平板版本的是layout-xhdpi

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <fragment
        android:id="@+id/new_list_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.foxdevelop.www.foxdevelopnews.NewsListFragment"/>
</FrameLayout>Code language: HTML, XML (xml)

17-平板版本主活动视图

平板版本的主活动视图 则复杂一点

它由两个碎片

一个是可变的 一个是固定

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment
        android:id="@+id/newlist_fragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:name="com.foxdevelop.www.foxdevelopnews.NewsListFragment"
        />
    <FrameLayout
        android:id="@+id/framelayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        >
        <fragment
            android:id="@+id/new_content_fragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="com.foxdevelop.www.foxdevelopnews.NewsDetailFragment"
            ></fragment>
    </FrameLayout>
</LinearLayout>Code language: HTML, XML (xml)

18-列表碎片中判断是手机或者平板

再列表碎片类中 我们需要给我们的RecyclerView设置不同的内容

在NewsListFragment中 我们要得到是上面版本的设备

//再活动创建的时候 触发的碎片事件 然后再这里 我们判断是手机还是平板isPhonePane是保存状态的
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    isPhonePane = getActivity().findViewById(R.id.new_list_fragment)!=null;
}Code language: JavaScript (javascript)

19-列表碎片类中添加适配器绑定RecylerView

我们需要添加一个适配器类

然后月需要添加一个ViewHolder 来缓存我们的数据的。

actionStart 基础课程中 我们说了 为了更方便大家使用活动 应该让这个 活动的启动交给活动类的创建者 所以我们添加这个方法 是为了让人家其他人的活动中调用 方便启动当前的这个互动

//  让其他活动来调用 启动我们这个活动
public static  void actionStart(Context context, String newTitle, String newContent)
{
    Intent intent = new Intent(context,NewsDetailActivity.class);
    intent.putExtra("news_title",newTitle);
    intent.putExtra("news_content",newContent);
    context.startActivity(intent);
}Code language: JavaScript (javascript)
package com.foxdevelop.www.foxdevelopnews;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

public class NewsListFragment extends Fragment {
    private  boolean isPhonePane;

    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_list_fragment,container,false);
        return view;
    }

    //再活动创建的时候 触发的碎片事件 然后再这里 我们判断是手机还是平板isPhonePane是保存状态的
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isPhonePane = getActivity().findViewById(R.id.new_list_fragment)!=null;
    }

    class  NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>
    {
        private List<News> newsList;

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.news_list_item,parent,false);
            final  ViewHolder holder = new ViewHolder(view);//  把子项存放在holder里面
            view.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    News newsInfo = newsList.get(holder.getAdapterPosition());
                    if (isPhonePane){
                        // 如果是手机单页模式下面
                        //手机我们直接启动活动就可以
                        NewsDetailActivity.actionStart(getActivity(),newsInfo.getTitle(),newsInfo.getContent());
                    }
                    else {
                        // 如果是平板版本的话
                        //平板只需要动态改变右侧的碎片就可以了
                        NewsDetailFragment newsDetailFragment =
                                (NewsDetailFragment) getFragmentManager().findFragmentById(R.id.new_content_fragment);
                        newsDetailFragment.SetNews(newsInfo.getTitle(),newsInfo.getContent());
                    }
                }
            });
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            holder.newTitleText.setText(newsList.get(position).getTitle());
        }

        @Override
        public int getItemCount() {
            return newsList.size();
        }

        public class ViewHolder extends  RecyclerView.ViewHolder{
            TextView newTitleText;

            public ViewHolder(View itemView) {
                super(itemView);
                newTitleText = (TextView) view.findViewById(R.id.news_list_textview);
            }
        }
    }
}Code language: PHP (php)

20-给RecyclerView设置数据

我们在碎片里面直接给RecyclerView赋值

正常来说应该是读取数据库 或者从WEB API等远程地址读取的。

21-异常问题处理

如何排查问题

我们在logcat 可以查看error信息

是从下往上查的

发现蓝色的 表示是我们字节的代码 View 写成了 view的

05-17 07:37:08.199 10738-10738/com.foxdevelop.www.foxdevelopnews E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.foxdevelop.www.foxdevelopnews, PID: 10738
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.foxdevelop.www.foxdevelopnews/com.foxdevelop.www.foxdevelopnews.MainActivity}: android.view.InflateException: Binary XML file line #19: Binary XML file line #20: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2925)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3060)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:110)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1800)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6649)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:826)
     Caused by: android.view.InflateException: Binary XML file line #19: Binary XML file line #20: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
     Caused by: android.view.InflateException: Binary XML file line #20: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:764)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:866)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at com.foxdevelop.www.foxdevelopnews.NewsDetailFragment.onCreateView(NewsDetailFragment.java:20)
        at android.support.v4.app.Fragment.performCreateView(Fragment.java:2346)
        at android.support.v4.app.FragmentManagerImpl.ensureInflatedFragmentView(FragmentManager.java:1664)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1399)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1659)
        at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1905)
        at android.support.v4.app.FragmentManagerImpl.onCreateView(FragmentManager.java:3715)
        at android.support.v4.app.FragmentController.onCreateView(FragmentController.java:114)
        at android.support.v4.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:374)
        at android.support.v4.app.BaseFragmentActivityApi14.onCreateView(BaseFragmentActivityApi14.java:39)
        at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:68)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:780)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:866)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
05-17 07:37:08.201 10738-10738/com.foxdevelop.www.foxdevelopnews E/AndroidRuntime:     at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287)
        at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139)
        at com.foxdevelop.www.foxdevelopnews.MainActivity.onCreate(MainActivity.java:11)
        at android.app.Activity.performCreate(Activity.java:7130)
        at android.app.Activity.performCreate(Activity.java:7121)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1262)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2905)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3060)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:110)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1800)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6649)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:826)Code language: PHP (php)

报错android.view.InflateException: Binary XML file line #11: Attempt to invoke virtual method ‘boolean

今天碰到个莫名其妙的报错

android.view.InflateException: Binary XML file line #11: Attempt to invoke virtual method ‘boolean java.lang.String.equals(java.lang.Object)’ on a null object reference

问题出在布局文件里,但是检查了好几遍也没发现问题在哪。最后终于找到了原因,真是尴尬!

<view style="@style/deviderLine"/>不知道各位看官有木有发现问题

<View style="@style/deviderLine"/>是的,View要大写!

05-17 07:49:07.079 11450-11450/com.foxdevelop.www.foxdevelopnews E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.foxdevelop.www.foxdevelopnews, PID: 11450
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
        at com.foxdevelop.www.foxdevelopnews.NewsListFragment$NewsAdapter.onBindViewHolder(NewsListFragment.java:90)
        at com.foxdevelop.www.foxdevelopnews.NewsListFragment$NewsAdapter.onBindViewHolder(NewsListFragment.java:53)Code language: PHP (php)

22-整个程序是思路理一理(完)

我们首先从主活动开始。理一理我们的整个应用的流程

主活动没有上面特点 就是在oncreate里面指明使用activity-main方法

然后通过限定符来匹配不同的布局视图

如果是手机版本 则很简单 就只有一个碎片

如果是平板版本 则两个碎片

手机版本的碎片是列表碎片

然后平板版本的是左侧是列表碎片

使用的是同一个碎片

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注