DrawerLayout
使用v4支持库中DrawerLayout可以实现侧滑菜单,在旧版本的v4库中没有DrawerLayout这个类,关联“android-support-v7-appcompat”库,这个库中的v4和我们项目中的v4包不是同一个版本,我们需要保持两个v4的版本一致,把v7库的v4复制到我们项目中即可。
修改activity_main.xml,使用DrawerLayout作为根布局,并添加菜单布局,如下:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- 主界面 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</RelativeLayout>
<!-- 菜单布局 -->
<FrameLayout
android:layout_width="240dp"
android:layout_height="match_parent"
android:background="@android:color/black"
android:layout_gravity="start" />
添加ActionBar
在Android 3.0(API 11)的时候出现了ActionBar,ActionBar替代了统计的标题栏,ActionBar不但可以显示标题,还可以显示应用图标,还可以增加选项菜单,还可以有导航功能。
要想让ActionBar在3.0版本以下也能使用,可使用v7支持库中的ActionBarActivity,用了ActionBarActivity后则必须使用一个Theme.AppCompat或它的子类主题 。
此时的ActionBar只有应用图标和标题,我们可以给ActionBar增加一个菜单按钮,在MainActivity的onCreate方法中调用initAcionBar方法,如下:
private void initActionBar() {
// 获取到一个向下兼容的ActionBar
ActionBar actionBar = getSupportActionBar();
// 设置在ActionBar的Home位置显示向上图标
actionBar.setDisplayHomeAsUpEnabled(true);
// 获取到抽屉布局
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
// 创建一个抽屉开关,用于控制抽屉的开和关
toggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_drawer_am, 0, 0);
// 设置在ActionBar的左上角显示为菜单图标
toggle.syncState();
// 监听抽屉的滑动事件,让ActionBar上的菜单按钮随着抽屉的滑动而滑动
drawerLayout.setDrawerListener(toggle);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (toggle.onOptionsItemSelected(item)) {
return true; // 如果抽屉开关返回了true则说明需要消费掉这个事件。
}
return super.onOptionsItemSelected(item);
实现主界面布局的左右切换
1、主界面顶部的标签实现使用开源控件“PagerSlidingTabStrip” 实现,把资料中的“PagerSlidingTabStrip”复制到我们的项目中,实现主界面布局,给PagerSlidingTabStrip设置一个背景:bg_tab.9.png,还有主界面也是有背景颜色的,用吸管吸取即可,完成主界面布局如下红色代码:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/base_bg" >
<!-- 主界面 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.itheima.googleplay.view.PagerSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_tab" />
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<!-- 菜单布局 -->
<FrameLayout
android:layout_width="240dp"
android:layout_height="match_parent"
android:background="@android:color/black"
android:layout_gravity="start" />
ViewPager要想工作必须给它设置一个PagerAdapter,而我们的ViewPager这里要装的内容是Fragment对象,所以用PagerAdapter的子类FragmentPagerAdapter会比较方便:
public class MainAdpater extends FragmentPagerAdapter {
private ArrayList<Fragment> fragments;
public MainAdpater(FragmentManager fm, ArrayList<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
/** 返回一个用于显示ViewPager页面的Fragment */
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
/** 返回ViewPager页面数量 */
@Override
public int getCount() {
return fragments.size();
}
/** 返回ViewPager页面的标题,PagerSlidingTabStrip会调用这个方法获取标题并显示出来 */
@Override
public CharSequence getPageTitle(int position) {
return fragments.get(position).getClass().getSimpleName();
}
}
上面的Adapter想要工作必须给它传入Fragment集合,创建一些Fragment类:
public class AFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, null);
TextView tv_info = (TextView) view.findViewById(R.id.tv_info);
tv_info.setText(getClass().getSimpleName()); // 显示类名
return view;
}
}
创建Adapter并传给ViewPager,然后再把ViewPager传给PagerSlidingTabStrip,在onCeate方法中调用如下代码:
private void initViewPagerAndTabs() {
// 初始化ViewPager
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
ArrayList<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(new AFragment());
fragments.add(new BFragment());
fragments.add(new CFragment());
fragments.add(new DFragment());
fragments.add(new EFragment());
fragments.add(new FFragment());
fragments.add(new GFragment());
viewPager.setAdapter(new MainAdpater(getSupportFragmentManager(), fragments));
// 初始化Tabs
PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs);
tabs.setViewPager(viewPager); // 把ViewPager和Tabs进行关联
如果想修改指示器的外观颜色,可调用如下方法:
tabs.setTabBackground(0x55FF0000, 0x00000000); // tab的背景:按下、正常状态的背景颜色
tabs.setTabTextColor(Color.GRAY, Color.BLACK); // tab的字体颜色:选择、正常状态的字体颜色
tabs.setTextSize(20); // tab的字体大小
tabs.setIndicatorColor(Color.BLACK); // 滑动指示线的颜色
tabs.setIndicatorHeight(6); // 滑动指标器的高
tabs.setUnderlineColor(Color.BLUE); // tabs底部完整宽的线的颜色
tabs.setUnderlineHeight(2); // tabs底部完整宽的线的高
tabs.setDividerColor(Color.RED); // tab之间的分隔线的颜色
tabs.setDividerPadding(8); // 分隔线的顶部和底部的padding
tabs.setTabPaddingLeftRight(8); // 设置tab左右两边的padding
----------------------------------------------------------------------
抽取的BaseFragment<span style="font-size:18px;">public abstract class BaseFragment extends Fragment implements JsonRequestCallback{
public StateFrameLayout rootView;
public Context context;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
context = getActivity();
rootView = (StateFrameLayout) inflater.inflate(R.layout.state_layout, null);
//创建第四种状态布局
rootView.setContentView(getContentView());
initView();
initListener();
//initData();
return rootView;
}
public <T> T findView(int id){
@SuppressWarnings("unchecked")
T view = (T)rootView.findViewById(id);
return view;
}
public boolean checkData(Collection<?> datas) {
boolean result = false;
if (datas == null) {
rootView.showFailView();
}else if(datas.isEmpty()){
rootView.showEmptyView();
}else {
rootView.showContentView();
return true;
}
return result;
}
//检查数据是否有效
public boolean checkData(HashMap<?, ?> datas) {
boolean result = false;
if (datas == null) {
rootView.showFailView();
}else if(datas.isEmpty()){
rootView.showEmptyView();
}else{
rootView.showContentView();
result = true;
}
return result;
}
public abstract Object getContentView();
public abstract void initData();
public abstract CharSequence getTitle();
public abstract void initListener();
public abstract void initView();
}</span>
封装StateLayout
每个Fragment界面都会有这4种状态,所以应该对它们进行封装,原理就是设计一个StateLayout容器,重叠存放上面的4个View,需要哪个View就显示哪个View即可。其中正在加载、加载失败、空这3种状态显示的界面内容在每个Fragment中显示的都一样,可以封装到StateLayout中写死,而加载到数据后显示的界面,对于每个Fragment都是不一样的界面,则这个界面不能写死到StateLayout中,只能提供一个添加主界面的方法,由它具体的Fragment去调用这个方法来添加主界面,StateLayout类设计如下:
public class StateFrameLayout extends FrameLayout {
private View state_loading;
private View state_fail;
private View state_empty;
private View contentView;
public StateFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StateFrameLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//加载布局后
@Override
protected void onFinishInflate() {
initView();
super.onFinishInflate();
}
private void initView() {
//三中状态
state_loading = findViewById(R.id.state_pbar);
state_fail = findViewById(R.id.state_fail);
state_empty = findViewById(R.id.state_empty);
showView(state_loading);
}
public void showLoadingView(){
showView(state_loading);
}
public void showFailView(){
showView(state_fail);
}
public void showEmptyView(){
showView(state_empty);
}
public void showContentView(){
showView(contentView);
}
private void showView(View view) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.setVisibility(child == view ? View.VISIBLE : View.GONE);
}
}
//第四种正常内容状态
public void setContentView(Object viewOrId){
if (viewOrId == null) {
throw new IllegalArgumentException("必须要给Fragemnt的getContentView方法返回一个布局id,或者设置一个View");
}else if(viewOrId instanceof Integer){
int id = (Integer) viewOrId;
contentView =View.inflate(getContext(), id, null);
}else {
contentView = (View) viewOrId;
}
//将conventView 添加到状态布局
addView(contentView);
//隐藏
contentView.setVisibility(View.GONE);
}
}
上面类的布局
<?xml version="1.0" encoding="utf-8"?>
<com.itheima.googleplay.view.StateLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ProgressBar
android:id="@+id/loadingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<LinearLayout
android:id="@+id/failView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_error_page"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载失败,点击重试"
android:textSize="16sp"
android:layout_margin="6dp"
android:textColor="#666666"
android:background="@drawable/btn_normal"
android:padding="6dp"/>
</LinearLayout>
<ImageView
android:id="@+id/emptyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_empty_page" />
在BaseFragment中即可使用这个拥有4种状态的布局
---------------------------------------------------------------------
在某一个界面被选择的时候调用上面的initData方法去联网获取数据
ViewPager默认最多只加载3页,多余的会被移除,我们的应用有7个页面,我们不希望被移除,所以设置默认加载7页,在MainActivity初始ViewPager的时候调用方法:viewPager.setOffscreenPageLimit(fragments.size())。 这会导致7个Fragment的onCreateView方法一开始就全部执行了,这个方法里面的initData方法随之会被调用,在这个方法中我们一般要请求网络数据,这样造成的问题是一次性加载了7个网络请求,我们希望是Fragment界面第一次显示的时候才去加载网络数据,所以我们需要将BaseFragment中onCreateView方法中调用的initData方法删除,然后在MainActivity中监听ViewPager界面的选择情况,当第一次选择并显示的时候才去调用initData方法
在MainActivity.onCreate方法中,初始化PagerSlidingTabStrip的时候设置一个页面改变监听器,如下,红色框为新增代码:
注意:不要把监听器设置给ViewPager
private ArrayList<Integer> positions = new ArrayList<Integer>();
OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
public void onPageSelected(int position) {
if (!positions.contains(position)) {
positions.add(position);
BaseFragment baseFragment = fragments.get(position);
baseFragment.initData();
}
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
public void onPageScrollStateChanged(int state) { }
};