仿网易新闻APP(三)——标题栏之24小时要闻

感冒了几天,今天总算正常了点,想了想我还有未完成的最后一类博文,马上就跳起来,接着奋斗了。废话就不多说了先上图看看网易新闻APP的标题栏24小时要闻如下:


  

我们即将完成的效果图:


   


图片显示0是默认图片,代表该新闻没有图片,不过,该程序员实现的功能我都实现了,这里面涉及的知识有平移动画,网络爬虫,汉字截取算法,各个编码中汉字占多少字节,异步加载(及自定义ListView添加头部加载特效及分页显示),这里为什么加个括号,因为这个功能不是本片文章所讲解的,一篇文章将前面那些功能已经够多了,等下下篇文章讲解完横向滑动菜单后,就会讲解listView设置的特效。


首先知识点得慢慢讲解,一下讲解篇幅太大无法讲解完全,所以,本文代码的漏洞,会在优化ListView中改进,就像Android编程权威指南一样,随着深入,慢慢优化。


1.标题栏布局文件


首先,我们来看看网易的标题栏效果图:



当然背景是红色,那么,请根据上篇文章在反编译的文件夹中找到这三个图片,下面是整个标题栏的布局文件lyj_title_bar_layout.xml代码:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/lyj_title_bar_layout_main"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#EB413D">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">

        <ImageView
            android:layout_width="57dp"
            android:layout_height="30dp"
            android:layout_marginLeft="8dp"
            android:background="@drawable/base_common_default_icon_small"
            android:scaleType="fitXY" />

        <ImageButton
            android:id="@+id/importantNews"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_toLeftOf="@+id/weather"
            android:background="@drawable/ic_msg_center_header"
            android:scaleType="fitXY" />

        <ImageButton
            android:id="@+id/weather"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentRight="true"
            android:background="@drawable/ic_newspage_menu_moreoverflow"
            android:scaleType="fitXY" />
    </RelativeLayout>
</LinearLayout>


这个相信不需要解释很多,一般都会懂得。


将此布局文件写进NewsFragment类的布局文件news_fragment_main_layout.xml中,代码如下:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/lyj_title_bar_layout"/>
    
</RelativeLayout>


运行后,将会得到如下图所示的效果:




虽然没有网易那么美,但是基本就是这样,里面的图片太多,我只找了几个很像的图片。


2.24小时要闻布局文件


下面是分析24小时要闻布局的截图:



important_news_layout.xml代码如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/important_news_layout_mymain"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:visibility="gone">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@drawable/biz_msg_center_header_day_bg"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageButton
                android:id="@+id/important_news_layout_back_but"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/abc_ic_ab_back_mtrl_am_alpha" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginTop="5dp"
                android:text="@string/important_news_layout_24txt"
                android:textColor="@android:color/white"
                android:textSize="18sp" />
        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:layout_width="72dp"
                android:layout_height="72dp"
                android:layout_gravity="center"
                android:layout_marginTop="50dp"
                android:background="@drawable/ic_msg_center_header" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="@string/important_news_layout_txt"
                android:textColor="@android:color/white"
                android:textSize="15sp" />

        </LinearLayout>

    </LinearLayout>

    <ListView
        android:id="@+id/important_news_layout_listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


同样是布局文件,大同小异而已。不过此布局文件默认是隐藏的,所以设置为GONE。


将此布局文件添加自NewsFragment的布局文件news_fragment_main_layout.xml中,代码如下:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/lyj_title_bar_layout"/>

    <include layout="@layout/important_news_layout"/>
    
</RelativeLayout>


3.平移动画


打开网易,点击24小时要闻,你会发现,此界面是从右向左滑动出来的,也就是平移出来的,这就是平移动画,那么动画有两种实现方式:


①通过引用XML文件里面已经写好的动画,实现动画效果。


②动态的使用代码写出动画。


显然,通过XML写动画,即简洁又美观。


我们通过XML写的动画同意放在res/anim文件夹下,下面的代码就是我们24小时要闻需要使用到的平移动画important_news_layout_show_anim.xml:


<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="100%"
        android:toXDelta="0%"
        android:fillAfter="true"/>
</set>


这个是滑动出来的代码,当然也有滑动消失的代码important_news_layout_hide_anim.xml:


<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="0%"
        android:toXDelta="100%"
        android:fillAfter="true"/>
</set>


我们来一条一条解释:


translate:就是平移动画,也是平移动画属性的父标签。

android:duration="500":动画执行时间半秒,一秒为1000。

android:fillAfter="true":动画结束时画面停留在最后一帧。

android:fromXDelta="100%",android:toXDelta="0%":以百分比表示,就是从自己X的100%,也就是布局的宽度,也就是右边界,X的0%,也就是左边界,这就是右平移到左。

同理android:fromXDelta="0%",android:toXDelta="100%":以百分比表示,就是从自己X的0%,也就是布局的宽度,也就是左边界,X的100%,也就是左边界,这就是左平移到右。

Y没写意味着不变。


NewsFragment.class执行动画的代码如下:


public class NewFragment extends Fragment {
    //24小时要闻代码片段
    private LinearLayout importLayout;//整个24小时要闻的布局文件
    private ImageButton importNews;//打开24小时新闻的按钮
    private ImageButton importBackBut;//关闭24小时新闻的回退按钮
    private ListView importListView;//24小时新闻的listView
    private ImportantNewsAdapter importAdapter;//listView对应的adapter

    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.news_fragment_main_layout,container,false);
        //24小时要闻代码片段
        this.importLayout=(LinearLayout)view.findViewById(R.id.important_news_layout_mymain);
        this.importNews=(ImageButton)view.findViewById(R.id.importantNews);
        this.importBackBut=(ImageButton)view.findViewById(R.id.important_news_layout_back_but);
        this.importListView=(ListView)view.findViewById(R.id.important_news_layout_listview);
        final Animation translateShow=AnimationUtils.loadAnimation(getActivity(), R.anim.important_news_layout_show_anim);//获取平移显示24小时新闻动画
        final Animation translateHide=AnimationUtils.loadAnimation(getActivity(), R.anim.important_news_layout_hide_anim);//获取平移隐藏24小时新闻动画
        //24小时新闻显示按钮
        this.importNews.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                importLayout.setAnimation(translateShow);//设置动画
                translateShow.start();//执行动画
            }
        });
        //24小时新闻隐藏按钮
        this.importBackBut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                importLayout.setAnimation(translateHide);//设置动画
                translateHide.start();//执行动画
                importLayout.setVisibility(View.GONE);//设置隐藏
                importAdapter=null;
            }
        });
        return view;
    }


写在XML之中的动画动过AnimationUtils.loadAnimation方法加载。这里有个动画漏洞,不过在这里不会显示出来,下篇讲解标题栏之天气按钮会具体讲解到,我的博文实战类篇就是这样,跟着开发进度讲解知识点,APP用到的知识都会讲,但讲解会跟着开发进度讲,开发到哪,讲到哪,没有碰到时候的不会多讲。后续都会讲到。


运行之后,你会看到你的动画效果已经和网易的一模一样了,只是这个时候出来的只有24小时新闻的上面部分,listview没有数据所以没有显示。下面来获取新闻。


4.改进的网络新闻爬虫



前面第一篇文章基本讲解的是怎么获取PC新闻条目,手机端新闻详情。为了统一获取地址,我们获取网易的手机新闻主页,也就是http://news.163.com/mobile/,网易新闻客户端获取的就是该地址的新闻条目。


下面是改进后的网络爬虫程序代码:


public class LYJJsoupWangYiTUtils {
    /***
     * 获取新闻详细文章
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static String jsoupNewsPage(String url) throws Exception {
        String page = null;
        byte[] htmlbyte = ConnectNetwork.getImageDat(url);
        String html = new String(htmlbyte, "UTF-8");
        Document doc = Jsoup.parse(html);
        Element div = doc.select("div.article-body").get(0);
        page = div.html().toString();
        return page;
    }

    /**
     * 获取文章描述和文章图片
     *
     * @param messageItem
     * @param aHref
     * @throws Exception
     */
    public static void jsoupNewsContentAndImage(MessageItem messageItem, String aHref) throws Exception {
        String html = new String(ConnectNetwork.getImageDat(aHref), "UTF-8");
        Document doc = Jsoup.parse(html);
        Elements divs = doc.select("div.article-body");
        if (divs.size() == 0) {
            System.out.println(aHref);
            return;
        }
        Element div = divs.first();
        Elements ps = div.getElementsByTag("p");
        if (ps.size() == 0) {
            return;
        }
        Element p = null;
        if (ps.size() <= 2) {
            p = ps.first();
        } else {
            for (int i = 0; i < ps.size(); i++) {
                if (ps.get(i).text().length() > 90) {
                    p = ps.get(i);
                    break;
                } else if (ps.get(i).text().length() > 0) {
                    p = ps.get(i);
                }
            }
        }
        messageItem.setContent(p.text());
        Elements imgs = div.getElementsByTag("img");
        if (imgs.size() == 0) {
            return;
        } else {
            String imgSrc = imgs.first().attr("src");
            messageItem.setImageSrc(ConnectNetwork.getImageDat(imgSrc));
        }
    }

    /**
     * 加载24小时要闻
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static List<MessageItem> jsoupImportantNews(String url) throws Exception {
        List<MessageItem> messageItemList = new ArrayList<>();
        byte[] htmlbyte = ConnectNetwork.getImageDat(url);
        String html = new String(htmlbyte, "GBK");
        Document doc = Jsoup.parse(html);
        Element sectionLabels = doc.select("section.ns-wnews").select("section.ns-mb40").get(1);
        Elements aLabels = sectionLabels.select("a");
        for (int i = 0; i < aLabels.size(); i++) {
            MessageItem item = new MessageItem();
            Element aLabel = aLabels.get(i);
            item.setTitle(aLabel.text());
            item.setHrefSrc(aLabel.attr("href"));
            jsoupNewsContentAndImage(item, aLabel.attr("href"));
            if (item.getContent() != null) {
                messageItemList.add(item);
            }
        }
        return messageItemList;
    }
}


开始的文章已经详细的解释过了,这里就不在阐述了。不过还是要提醒一下,我们获取的是这里的新闻:



因为我们我们优化ListView会在讲解完横向滑动菜单后讲解,所以这里,只获取了几条新闻,等优化ListView后我们回来重新写代码的。因为优化ListView是一章文章的篇幅,必须单独出来,否则一篇博文内容太多,会降低质量。



5.ListView适配器



为了方便扩展,我们选择最简单的也是最复杂的BaseAdapter。我们可以看到,新闻的第一张图片,与下面的不是一个格式,一个是垂直模式,一个是水平模式,所以我们在布局文件中,将显示内容的三个控件,放在了一个LinearLayout中,便于控制显示方式。important_listview_item.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/important_listview_item_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/important_listview_item_image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:scaleType="fitXY"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/important_listview_item_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/important_listview_item_description"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </LinearLayout>

    <ImageView
        android:id="@+id/important_listview_item_topleftbg"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:scaleType="fitXY"
        android:background="@drawable/biz_msg_center_flag_bg"/>

    <TextView
        android:id="@+id/important_listview_item_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="13dp"
        android:textColor="@android:color/white"/>
</RelativeLayout>





下面是继承适配器BaseAdapter的ImportantNewsAdapter代码:

public class ImportantNewsAdapter extends BaseAdapter{
    private ArrayList<SoftReference<Bitmap>> mBitmapRefs = new ArrayList<SoftReference<Bitmap>>();
    private List<MessageItem> messageItemList = new ArrayList<>();
    private Context context;

    public ImportantNewsAdapter(List<MessageItem> items, Context context) {
        this.context=context;
        this.messageItemList=items;
    }

    @Override
    public int getCount() {
        return messageItemList.size();
    }

    @Override
    public Object getItem(int position) {
        return messageItemList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        View view = convertView;
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.important_listview_item, null);
            viewHolder=new ViewHolder(view);
            view.setTag(viewHolder);
        } else {
            viewHolder=(ViewHolder)view.getTag();
        }
        MessageItem messageItem=messageItemList.get(position);
        if(position==0){//当第一条数据的时候,布局格式
            viewHolder.layout.setOrientation(LinearLayout.VERTICAL);
            LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)viewHolder.imageView.getLayoutParams();
            params.height=ApplyUtils.dip2px(context,200);
            params.width=LinearLayout.LayoutParams.MATCH_PARENT;
            params.setMargins(0,20,0,0);
            viewHolder.imageView.setLayoutParams(params);
            viewHolder.topLeftBg.setImageResource(R.drawable.biz_msg_center_flag_first_bg);
        }else{//其他时候的布局格式
            viewHolder.layout.setOrientation(LinearLayout.HORIZONTAL);
            LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)viewHolder.imageView.getLayoutParams();
            params.height=ApplyUtils.dip2px(context,80);
            params.width=ApplyUtils.dip2px(context,80);
            viewHolder.imageView.setLayoutParams(params);
            viewHolder.topLeftBg.setImageResource(R.drawable.biz_msg_center_flag_bg);
        }
        if(messageItem.getImageSrc()!=null){
            ByteArrayInputStream bais=new ByteArrayInputStream(messageItem.getImageSrc());
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 2;
            Bitmap bitmap= BitmapFactory.decodeStream(bais,null,options);
            mBitmapRefs.add(new SoftReference<Bitmap>(bitmap));
            viewHolder.imageView.setImageBitmap(bitmap);
        }else{
            viewHolder.imageView.setImageResource(R.drawable.biz_more_menu_t0);//当该文章没有图片的时候的默认图片,所以你才会看到,文章开头的那个0。
        }
        viewHolder.numberTxt.setText(String.valueOf(position+1));
        viewHolder.titleTxt.setText(messageItem.getTitle());
        try {
            viewHolder.contentTxt.setText(ApplyUtils.substring(messageItem.getContent(),90)+"...");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return view;
    }

    class ViewHolder {
        private ImageView imageView;
        private TextView numberTxt;
        private TextView titleTxt;
        private TextView contentTxt;
        private LinearLayout layout;
        private ImageView topLeftBg;

        ViewHolder(View view) {
            this.imageView = (ImageView) view.findViewById(R.id.important_listview_item_image);
            this.titleTxt = (TextView) view.findViewById(R.id.important_listview_item_title);
            this.contentTxt = (TextView) view.findViewById(R.id.important_listview_item_description);
            this.numberTxt = (TextView) view.findViewById(R.id.important_listview_item_number);
            this.layout = (LinearLayout) view.findViewById(R.id.important_listview_item_layout);
            this.topLeftBg = (ImageView) view.findViewById(R.id.important_listview_item_topleftbg);
        }
    }
}


这是ListView的通用模式,不毕过多解释,这里用到的软引用,不过我并没有完善它,它将会在优化ListView章节讲解到,如何配合引用队列使用它,这里跳过,你这里用不用都没有多大关系。


需要解释了还有以下几点:

①.汉字截取

汉字和字母符号在如下编码中,占多少字节:

英文字母:A

字节数:1;编码:GB2312

字节数:1;编码:GBK

字节数:1;编码:GB18030

字节数:1;编码:ISO-8859-1

字节数:1;编码:UTF-8

字节数:4;编码:UTF-16

字节数:2;编码:UTF-16BE

字节数:2;编码:UTF-16LE


中文汉字:人

字节数:2;编码:GB2312

字节数:2;编码:GBK

字节数:2;编码:GB18030

字节数:1;编码:ISO-8859-1

字节数:3;编码:UTF-8

字节数:4;编码:UTF-16

字节数:2;编码:UTF-16BE

字节数:2;编码:UTF-16LE


我们可以看到,我们从网站获取的内容简介是UTF-8模式存取在字符串String中的,但是在java的String内部汉字当成一个字节的字符(UCS2字符)处理了。所以遍历的时候截取汉字只用一个字符就可以,但是却在UTF-8中跳过了3个字符。举例说明:


假如有个字符串String str="我是帅哥火";存储的时候用的是UTF-8编码。

所以String的.getBytes("UTF-8").length字节数是15。但是str.charAt(0),将获取“我“这个汉字,但是在UTF-8中你已经跳过了2个字节,默认截取第二个字符同样是str.charAt(1),得到“是”。


但是假如我的字符串是String str="我liyuanjing是shuaige";存储的时候用的是UTF-8编码。

所以String的.getBytes("UTF-8").length字节数是23。但是我要截取多少字符算的是汉字和符号或字母,假如我要截取“我liyuanjing是”,那么需要输入的是16,但是截取只用了12,少了4个字节的差值,这就是下面截取字符串方法为什么每截取一个汉字,总数要减去2的原因。


截取汉字的ApplyUtils.class代码如下:


public static boolean isChineseChar(char c) throws UnsupportedEncodingException {          // 如果字节数大于1,是汉字
    return String.valueOf(c).getBytes("UTF-8").length > 1;
}

public static String substring(String orignal, int count)
        throws UnsupportedEncodingException {
    // 原始字符不为null,也不是空字符串
    if (orignal != null && !"".equals(orignal)) {
        // 将原始字符串转换为UTF-8编码格式
        orignal = new String(orignal.getBytes(), "UTF-8");//
        // System.out.println(orignal);
        //System.out.println(orignal.getBytes().length);
        // 要截取的字节数大于0,且小于原始字符串的字节数
        if (count > 0 && count < orignal.getBytes("UTF-8").length) {
            StringBuffer buff = new StringBuffer();
            char c;
            for (int i = 0; i < count; i++) {
                c = orignal.charAt(i);
                buff.append(c);
                if (ApplyUtils.isChineseChar(c)) {
                    // 遇到中文汉字,截取字节总数减2
                    --count;
                    --count;
                }
            }
            return new String(buff.toString().getBytes(),"UTF-8");
        }
    }
    return orignal;
}



这段虽然是Copy别人的,但是那个网址的代码有个错误,我这里改正了,原理我也讲清楚了。希望大家应该听懂了。


②dp转换成px。


动态代码中,并不能设置成dp格式,只有px格式,这个时候,就需要把你常用的dp转换成px。


dp是虚拟像素,在不同的像素密度的设备上会自动适配

在320x480分辨率,像素密度为160,1dp=1px
在480x800分辨率,像素密度为240,1dp=1.5px
计算公式:1dp*像素密度/160 = 实际像素数


那么就得到如下代码:


public static int dip2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}


至于为什么会是这样,你把这段代码拷贝到Android Studio中,点击getDisplayMetrics()查看源代码,你就清楚,就是上面的公式进行换算的。


6.异步加载


这里简单的用AsyncTask,到优化listView博文会具体改进的。


代码在NewsFragment.class中:


private class ImportAsyncTask extends AsyncTask<Void,Integer,List<MessageItem>>{

    @Override
    protected List<MessageItem> doInBackground(Void... params) {
        List<MessageItem> messageItemList=null;
        try {
            messageItemList=LYJJsoupWangYiTUtils.jsoupImportantNews("http://news.163.com/mobile/");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return messageItemList;
    }

    @Override
    protected void onPostExecute(List<MessageItem> items) {
        if(items.size()==0){

        }else{
            importAdapter=new ImportantNewsAdapter(items,getActivity());
            importListView.setAdapter(importAdapter);
        }
    }
}


在打开24小时要闻中调用它:


//24小时新闻显示按钮
this.importNews.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        importLayout.setAnimation(translateShow);//设置动画
        translateShow.start();//执行动画
        importLayout.setVisibility(View.VISIBLE);//显示界面
        new ImportAsyncTask().execute();//加载网络新闻
    }
});



本章节的源代码如下:


http://download.csdn.net/detail/liyuanjinglyj/9223397


运行程序,得到如下结果:



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值