1. 程式人生 > >Android應用經典主介面框架之二:仿網易新聞客戶端、CSDN 客戶端 (Fragment ViewPager)

Android應用經典主介面框架之二:仿網易新聞客戶端、CSDN 客戶端 (Fragment ViewPager)

第二種主介面風格則是以網易新聞、鳳凰新聞以及新推出的新浪部落格(閱讀版)為代表,使用ViewPager+Fragment,即ViewPager裡介面卡裡放的不是一般的View,而是Fragment。所以介面卡不能繼承PagerAdapter,而要繼承FragmentPagerAdapter,這是在android.support.v4.app.FragmentPagerAdapter包裡的。有點奇葩的是,FragmentPagerAdapter只在這個包裡有,在android.app.*這個包下面麼有。到後面會發現,只能用android.support.v4.app.*包下面的東西。兩個包裡的FragmentManager是不通用的,而且兩個包裡提供的Fragment也不大一樣。如果繼承android.app.*下的Fragment,則不能重新寫建構函式,只能用預設的。v4的包裡麼有這個限制。

下圖是網易新聞、鳳凰新聞、新浪部落格的截圖:


關於仿網易新聞客戶端程式碼已經很多了,本人主要根據開源的這個CSDN客戶端的製作,準備一步步搞下。這本是一個大牛之作發在oschina上,參考連結裡分5步去實現。我看了它的程式碼,是染在一起的。比如要完這個導航不需要額外的三個包,而他的資源裡是弄一起的。所以準備自己玩玩,順便記錄開發中的問題。

第一步:最上面的導航欄

即有“網易新聞”四個大字這一欄。佈局檔案head_title_panel.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="wrap_content"
    android:background="@color/light_blue"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/headIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="4dp"
        android:src="@drawable/biz_navigation_tab_news_pressed" />

    <ImageView
        android:id="@+id/headDivider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:src="@drawable/base_action_bar_back_divider" />

    <TextView
        android:id="@+id/headTV"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="4dp"
        android:layout_weight="1"
        android:text="CSDN資訊"
        android:textColor="@color/white"
        android:textSize="21sp"
        android:textStyle="bold">
    </TextView>


</LinearLayout>
為了日後操作上的方便,我將它對映成一個HeadTitlePanel.java檔案,可以看到這種寫法跟上篇 上下panel的定義是有點區別的。
package org.yanzi.ui;

import org.yanzi.csdnproject.R;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class HeadTitlePanel extends RelativeLayout {

	private Context mContext;
	private TextView mHeadTV;
	private ImageView mHeadIcon;
	private ImageView mHeadDivider;
	
	public HeadTitlePanel(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		mContext = context;
		View parent = LayoutInflater.from(mContext).inflate(R.layout.head_title_panel, this, true);
		mHeadTV = (TextView) parent.findViewById(R.id.headTV);
	}

	

}

第二步:ViewPager的導航欄

這個本來我是準備自己封裝個的,網上也有用Radiobutton封裝的,考慮到它這個導航欄還是不固定長度可以滑動的,時間原因暫時不封裝了,使用開源Android-ViewPagerIndicator-master.zip 這個包,這個人的github連結:https://github.com/JakeWharton 將其中的library資料夾改名ViewPagerIndicator_library導進來。這個裡面有好幾種Indicator,我們主要用TabPageIndicator這個。

第三步:MainActivity的佈局:

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#eee"
    tools:context=".MainActivity" >

    <org.yanzi.ui.HeadTitlePanel
        android:id="@+id/head_title_panel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true" />

    <com.viewpagerindicator.TabPageIndicator
        android:id="@+id/page_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/head_title_panel"
        android:background="@color/transparentblue" />

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/page_indicator"
       />

</RelativeLayout>
MainActivity.java
package org.yanzi.csdnproject;

import org.yanzi.viewpager.adapter.TabAdapter;

import com.viewpagerindicator.TabPageIndicator;

import android.os.Bundle;
import android.app.Activity;
import android.app.FragmentManager;
import android.view.Menu;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;

public class MainActivity extends FragmentActivity {
private TabPageIndicator mPageIndicator;
private ViewPager mViewPager;
private FragmentPagerAdapter fragPagerAdapter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initUI();
		
		fragPagerAdapter = new TabAdapter(getSupportFragmentManager());
		mViewPager.setAdapter(fragPagerAdapter);
		mPageIndicator.setViewPager(mViewPager, 0);
		
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	private void initUI(){
		mPageIndicator = (TabPageIndicator)findViewById(R.id.page_indicator);
		mViewPager = (ViewPager)findViewById(R.id.view_pager);
	}

}

出奇的簡單,比單純用Fragment還簡單,原因是隻需把Fragment塞到介面卡裡就ok了,介面卡為我們做了Fragment的切換等工作,我們能做的也就是在介面卡裡new Fragment的時候判斷是否已存在。以下幾點需要注意:

1、在styles.xml裡它定義了樣式:

    <style name="MyTheme" parent="AppBaseTheme">
        <item name="vpiTabPageIndicatorStyle">@style/MyWidget.TabPageIndicator</item>
        <item name="android:windowBackground">@drawable/init_pic</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:animationDuration">5000</item>
        <item name="android:windowContentOverlay">@null</item>
    </style>
    <style name="MyWidget.TabPageIndicator" parent="Widget">
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/vpi__tab_indicator</item>
        <item name="android:paddingLeft">22dip</item>
        <item name="android:paddingRight">22dip</item>
        <item name="android:paddingTop">8dp</item>
        <item name="android:paddingBottom">8dp</item>
        <item name="android:textAppearance">@style/MyTextAppearance.TabPageIndicator</item>
        <item name="android:textSize">16sp</item>
        <item name="android:maxLines">1</item>
    </style>

    <style name="MyTextAppearance.TabPageIndicator" parent="Widget">
        <item name="android:textStyle">bold</item>
        <item name="android:textColor">@color/black</item>
    </style>

這個是依賴於導進去的包的。

2、它這裡用了android:windowBackground的屬性,所以app開啟瞬間會有圖片彈出,之後設定MainActivity的佈局背景為android:background="#eee",又把圖片替換了。如果不設android:background="#eee" 會一直看到這個圖片不消失。

3、因為開篇講的原因,MainActivity只能繼承自FragmentActivity。

第四步:MainFragment.java,此類繼承Fragment,且是android.support.v4.app.Fragment下的。

package org.yanzi.fragment;

import org.yanzi.csdnproject.R;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class MainFragment extends Fragment {
	private int mNewsType = 0;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		View v = inflater.inflate(R.layout.tab_item_fragment_main, null);
		TextView tip = (TextView) v.findViewById(R.id.id_tip);
		Bundle b = getArguments();
		String title = b.getString("TITLES");
		tip.setText(title);
		return v;
	}
	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onActivityCreated(savedInstanceState);
	}
	

}
就是弄了一個佈局,然後從中取得引數並顯示。

第五步:ViewPager的介面卡TabAdapter.java

package org.yanzi.viewpager.adapter;

import org.yanzi.constant.Constant;
import org.yanzi.fragment.MainFragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class TabAdapter extends FragmentPagerAdapter {

	public TabAdapter(FragmentManager fm) {
		
		super(fm);
		// TODO Auto-generated constructor stub
	}

	@Override
	public Fragment getItem(int position) {
		// TODO Auto-generated method stub
		MainFragment fragment = new MainFragment();
		Bundle b = new Bundle();
		String title = Constant.TITLES[position];
		b.putString("TITLES", title);
		fragment.setArguments(b);
		return fragment;
	}

	@Override
	public CharSequence getPageTitle(int position) {
		// TODO Auto-generated method stub
		return Constant.TITLES[position];
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return Constant.TITLES.length;
	}

}

主要是重寫getItem,在這裡例項化Fragment,同時設傳遞的引數。其實這裡可以通過
FragmentManager
按照Tag查詢對應的Fragment是否存在,再進行例項化。

另外,getPageTitle這個介面必須重寫,其實看TabPageIndicator.java這個類的實現可以發現,它需要傳進去一個ViewPager,然後獲得介面卡,從介面卡裡得到Title。因為用了這個開源包,不能再用ViewPager的setOnPageChangeListener介面,只能用mPageIndicator.setOnPageChangeListener(listener)進行監聽。

完畢,原始碼連結:連結:http://pan.baidu.com/s/1bn4EFbp 密碼:dx4s

效果如下: