客户端开发之界面框架的实现

这篇文章中我只讨论界面主题框架的实现,为了满足大多数人的需求,讲的比较基础。客户端是采用了目前比较流行的设计方式:

QQ图片20150101141630.jpg

底部的tab菜单是总的导航,每点击一个tab将显示不同的内容。

一、主界面的实现

注意到上图中整个界面是三部分组成的actionbar、内容区域、以及底部tab选项卡。

actionbar部分只要设置成holo主题自然就会有,但是下面的内容区域和tab选项卡部分是这样布局的activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FFFFFF">
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
    />    
    <include layout="@layout/main_footer" />  
</LinearLayout>

其中内容区域是一个ViewPager,占满了除开actionbar和tab选项卡之外的所有内容。这是如何做到的呢?

<include layout="@layout/main_footer

这里面是一个高度写死了的布局。然后在ViewPager中,我们让       

android:layout_height="0dip"
android:layout_weight="1"

就可以实现ViewPager自动占满。

这样上中下布局就实现了。

二、底部Tab效果的实现

底部tab就是上面提到的main_footer中包含的内容。

实现的思路

底部的tab切换效果我们是使用RadioGroup来实现的,使用RadioGroup的理由有如下几点:

(1)RadioGroup中的元素有state_checked、state_pressed、state_focused三种基本状态,可以在drawable中对这三种状态分别设置不同颜色或者图标。

(2)RadioGroup不同元素之间的state_checked状态是互斥的,当其中一个被选中其他的元素会自动变为未选中。

(3)RadioGroup自带对选中变化事件的监听:onCheckedChanged。

当然有一个理由阻止你选择RadioGroup,那就是RadioGroup默认的样式太难看(圆圈里面一个点),而你往往又不知道其实这种样式是可以轻易改变的:

只需一行xml代码就可以改变RadioGroup的默认样式:

android:button="@null"

因此tab部分我们就确定使用RadioGroup了

代码

下面是tab部分的xml代码:main_footer.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:netmoon="http://schemas.android.com/apk/res/cn.netmoon.netmoondevicemanager"
    android:id="@+id/main_linearlayout_footer"
    android:layout_width="fill_parent" 
    android:layout_height="55dip"
    android:background="#33333333"
    >
    <RelativeLayout 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content">
        <RadioGroup android:gravity="center_vertical" android:orientation="horizontal" android:id="@+id/rg_tabs"  android:paddingTop="1.0dip" android:paddingRight="0.0dip"  android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true">
            <RadioButton android:gravity="center" android:id="@+id/tab_blog" android:background="@drawable/bottom_tab_selector" android:paddingTop="4.0dip" android:layout_width="fill_parent" android:layout_height="wrap_content" android:button="@null" android:text="博客" android:drawableTop="@drawable/tab_blog" android:layout_weight="1.0" style="@style/GreenTabBar" />
            <RadioButton android:gravity="center" android:id="@+id/tab_code" android:background="@drawable/bottom_tab_selector" android:paddingTop="4.0dip" android:layout_width="fill_parent" android:layout_height="wrap_content" android:button="@null" android:text="代码" android:drawableTop="@drawable/tab_code" android:layout_weight="1.0" style="@style/GreenTabBar" />
            <RadioButton android:gravity="center" android:id="@+id/tab_ask" android:background="@drawable/bottom_tab_selector" android:paddingTop="4.0dip" android:layout_width="fill_parent" android:layout_height="wrap_content" android:button="@null" android:text="问答" android:drawableTop="@drawable/tab_ask" android:layout_weight="1.0" style="@style/GreenTabBar" />
            <RadioButton android:gravity="center" android:id="@+id/tab_my" android:background="@drawable/bottom_tab_selector" android:paddingTop="4.0dip" android:layout_width="fill_parent" android:layout_height="wrap_content" android:button="@null" android:text="我的" android:drawableTop="@drawable/tab_my" android:layout_weight="1.0" style="@style/GreenTabBar" />
        </RadioGroup>
    </RelativeLayout>   
</LinearLayout>

使用android:button="@null" android:drawableTop="@drawable/tab_blog" 两个属性可以去掉RadioButton的默认圆圈,同时为RadioButton增加一个图标。为什么不同的状态下图标可以呈现不同的颜色呢,请看tab_blog.xml的定义:

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="false" android:drawable="@drawable/widget_bar_news_nor" />
    <item android:state_checked="true" android:drawable="@drawable/widget_bar_news_over" />
    <item android:state_focused="true" android:drawable="@@drawable/widget_bar_news_over" />
</selector>

tab_blog其实是一个selector,有三种状态,其中选中和获取焦点状态下图标为widget_bar_news_over.png,而未选中状态下图标为widget_bar_news_nor.png这两个图标只有一个区别,颜色不同。

同样的道理要使RadioButton的文字颜色不同我只需设置style属性:style="@style/GreenTabBar"

GreenTabBar.xml的定义(在values/style.xml中)

    <style name="GreenTabBar" parent="AppBaseTheme">
        <item name="android:textColor">@drawable/green_tabbar_reverse_drawable</item>
    </style>

然后green_tabbar_reverse_drawable.xml 就是描述文字颜色的drawable了:

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="@color/green" />
    <item android:state_pressed="true" android:color="@color/green" />
    <item android:state_focused="true" android:color="@color/green" />
    <item android:state_enabled="true" android:state_checked="false" android:color="@color/color_tabbar_text_gray" />
    <item android:state_enabled="true" android:state_pressed="false" android:color="@color/color_tabbar_text_gray" />
    <item android:state_focused="false" android:state_enabled="true" android:color="@color/color_tabbar_text_gray" />
    <item android:state_enabled="false" android:state_checked="false" android:color="@color/color_gray" />
    <item android:state_enabled="false" android:state_pressed="false" android:color="@color/color_gray" />
    <item android:state_focused="false" android:state_enabled="false" android:color="@color/color_gray" />
</selector>

可见设置文字颜色比设置图标多了一个步骤。green_tabbar_reverse_drawable.xml的状态要比之前描述图标的多了很多,其实应该以这个为准。

同样我们还可以设置不同状态下RadioButton的背景颜色(android:background="@drawable/***" ),就不赘述了。

三、监听选项卡切换事件并改变主内容区域

在MainActivity中实现的,下面是主要代码:

public class MainActivity extends FragmentActivity implements RadioGroup.OnCheckedChangeListener{
    private RadioGroup mRadioGroup;
    private ViewPager mPager;
    private TabAdapter mAdapter;
    private ArrayList<Fragment> mFragments;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPager = (ViewPager)findViewById(R.id.pager);
        mRadioGroup  = (RadioGroup)findViewById(R.id.rg_tabs);
        mRadioGroup.check(R.id.tab_blog); 
        mRadioGroup.setOnCheckedChangeListener(this);
        
        mFragments = new ArrayList<Fragment>();
        mFragments.add(new CategoryGridFragment());
        mFragments.add(CodeListFragment.newInstance());
        mFragments.add(new QuestionListFragment());
        mFragments.add(new CategoryGridFragment());
        mAdapter = new TabAdapter(getSupportFragmentManager(),mFragments);
        mPager.setAdapter(mAdapter);
        mPager.setOffscreenPageLimit(5);       
    }
    @Override
    public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
        
        switch (checkedId){
            case R.id.tab_blog:
                mPager.setCurrentItem(0,false);
                break;
            case R.id.tab_code:
                mPager.setCurrentItem(1,false);
                break;    
            case R.id.tab_ask:
                mPager.setCurrentItem(2,false);
                break;                    
        }
    }
}

通过mRadioGroup.setOnCheckedChangeListener(this); 我们将监听的任务交给了MainActivity自身,因此MainActivity必须实现onCheckedChanged方法。

在onCheckedChanged方法中,根据不同的选中状态,通过mPager.setCurrentItem来改变ViewPgaer显示的页,就改变了主内容区域的内容。

从上面的代码中还可以看到,ViewPager的每一个Page是一个Fragment,一个Fragment维护着一个界面。这几个Fragment分别是:CategoryGridFragment、CodeListFragment、QuestionListFragment、因为第四个界面还没有写好,第四个fragment暂时还是CategoryGridFragment。

在接下来的文章中我们将分别对这几个模块做介绍。