谷歌市场项目代码详解(一)

本文详细解析谷歌市场项目的代码实现,包括如何使用DrawerLayout创建侧滑菜单,确保版本一致性。同时,介绍如何添加ActionBar以及在3.0以下版本的支持,通过PagerSlidingTabStrip实现主界面布局的左右切换。此外,文章还探讨了StateLayout的设计,用于封装不同状态的显示,并讨论了如何在Fragment中适当地加载网络数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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)的时候出现了ActionBarActionBar替代了统计的标题ActionBar不但可以显示标题,还可以显示应用图标,还可以增加选项菜单,还可以有导航功能

想让ActionBar3.0版本以下也能使用,可使用v7支持库中的ActionBarActivityActionBarActivity后则必须使用一个Theme.AppCompat子类主题 

时的ActionBar只有应用图标和标题,我们可以给ActionBar增加一个菜单按钮,MainActivityonCreate方法中调用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容器,重叠存放上面的4View,需要哪个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())。 导致7FragmentonCreateView方法开始就全部执了,个方法里面的initData方法随之会被调用,这个方法中我们一般要请求网络数据,这样造成的问题是一次性加载7网络请求,我们希望是Fragment第一次显示的时候才去加载网络数据所以我们需要BaseFragmentonCreateView方法中调用的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) { }
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值