如此抄袭Apps之OscHub(一)
来源:互联网 发布:法兰克编程教学 编辑:程序博客网 时间:2024/05/29 17:30
前言
在开源中国推出Android客户端时就想研究其源码了,只可惜行动远不及心动来得快。趁着心未老,脚力尚可的时候,多搬几块砖吧。接下来的几天,我就和大家一起来学习开源中国的Android客户端的源码。大家也知道,阅读源码是学习编程的一种“捷径”,然而单纯地读源码却很难坚持下去。这次,我就打破常规,以一种“以写代读”的方式来重写一遍应用。我将这个“新”的应用命名为OscHub,因它的实现基本遵照了原客户端的实现方式,故名曰“抄袭”。OscHub的一切版权仍归开源中国所有。
话不多说,既然是抄袭*开源中国*Android客户端,我们首先应该拿到其源码,以方便更好的“抄袭”。下载源码请移步这里。
现在我们已经有了抄袭的材料,就开启我们的抄袭之路吧。
打开你的Android Studio(Eclipse用户请自行解决),创建一个名为OscHub的工程(你也可以起一个任何你喜欢的工程名)。这里我使用了自己的包名ml.uuku.oschub,大家也可以替换成自己喜欢的包名。
工程创建好后,我们直接用开源中国客户端的res目录(除了layout目录)替换掉自己工程的res目录,并修改strings.xml的app_name值为我们的应用名OscHub。只所以没有替换layout目录,是因为layout目录中的很多布局文件使用了自定义的控件,由于我们目前还没有创建这些控件,会导致IDE无法运行我们的“半成品”。与此同时,我们的应用仍然遵照原客户端的包结构形式,在编写代码的工程中,所有类名仍然与原客户端的类名一致。这样做的目的就是为了方便大家能随时查看原代码,并且能将精力聚焦到业务代码的书写,而不用去考虑每个页面的布局和交互效果。后面如果有时间,我会和大家一起来去分析这些布局文件,尤其是theme相关的文件和交互效果的实现,如drawable和color目录下的文件。有兴趣的朋友也可以自己去实现这些布局相关的文件。
我们的应用只支持4.0及以上版本(Level==14),所以原代码中有关版本兼容性问题在OscHub中并没有完全保留。另,所有类文件所在的包都依照原代码包结构,后文中只会提到类名,而没有指定全限定名,请读者朋友参照源码阅读此系列文章。
下面正式开始码字了
在包base下创建类BaseApplication
并继承自Application
。本类中主要提供了对于SharedPreferences
的操作
public class BaseApplication extends Application { private static String PREF_NAME = "osch_createivelocker.pref"; static Context _context; static Resources _resources; @Override public void onCreate() { super.onCreate(); _context = getApplicationContext(); _resources = getResources(); } public static synchronized BaseApplication context() { return (BaseApplication) _context; } public static Resources resources() { return _resources; } public static SharedPreferences getPreferences() { return context().getSharedPreferences(PREF_NAME, Context.MODE_MULTI_PROCESS); } public static SharedPreferences getPreferences(String prefName) { return context().getSharedPreferences(prefName, Context.MODE_MULTI_PROCESS); } public static void set(String key, int value) { SharedPreferences.Editor editor = getPreferences().edit(); editor.putInt(key, value); editor.apply(); } public static void set(String key, boolean value) { SharedPreferences.Editor editor = getPreferences().edit(); editor.putBoolean(key, value); editor.apply(); } public static void set(String key, String value) { SharedPreferences.Editor editor = getPreferences().edit(); editor.putString(key, value); editor.apply(); } public static boolean get(String key, boolean defValue) { return getPreferences().getBoolean(key, defValue); } public static String get(String key, String defValue) { return getPreferences().getString(key, defValue); } public static int get(String key, int defValue) { return getPreferences().getInt(key, defValue); } public static long get(String key, long defValue) { return getPreferences().getLong(key, defValue); } public static float get(String key, float defValue) { return getPreferences().getFloat(key, defValue); }}
然后创建一个BaseApplication
的子类AppContext
:
public class AppContext extends BaseApplication { /** * 是否是首次启动 */ public static boolean isFirstStart() { return getPreferences().getBoolean(AppConfig.KEY_FIRST_START, false); } /** * 夜间模式 */ public static boolean getNightModeSwitch() { return getPreferences().getBoolean(AppConfig.KEY_NIGHT_MODE_SWITCH, false); } /** * 设置夜间模式 */ public static void setNightModeSwitch(boolean on) { }}
记得在我们的AndroidManifest.xml
中配置该Application哦:)
为方便对所有Activity的管理,我们提供一个Activity管理器AppManager
:
public class AppManager { private static Stack<Activity> activityStack; private static AppManager instance; private AppManager() { } /** * 单例模式 */ public static AppManager getAppManager() { if (instance == null) { instance = new AppManager(); } return instance; } /** * 将启动的Activity存入堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack<Activity>(); } activityStack.add(activity); } /** * 从堆栈中取出当前的Activity(栈顶元素), 当不销毁栈顶元素 */ public Activity currentActivity() { return activityStack.lastElement(); } /** * 结束当前Activity(栈顶元素) */ public void finishActivity() { Activity activity = activityStack.pop(); if (activity != null && !activity.isFinishing()) { activity.finish(); activity = null; } } /** * 结束特定的Activity */ public void finishActivity(Activity activity) { if (activity != null && !activity.isFinishing()) { activityStack.remove(activity); activity.finish(); activity = null; } } /** * 结束指定类名的Activity */ public void finishActivity(Class<?> cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); break; } } } /** * 获取指定的Activity,没有则为null */ public Activity getActivity(Class<?> cls) { if (null != activityStack) { for (Activity activity : activityStack) { if (null != activity && activity.getClass().equals(cls)) { return activity; } } } return null; } /** * 结束所有的Activities */ public void finishAllActivity() { for (int i = 0, size = activityStack.size(); i < size; i++) { if (null != activityStack.get(i)) { finishActivity(activityStack.get(i)); } } activityStack.clear(); } /** * 退出App */ public void AppExit(Context context) { try { finishAllActivity(); // 杀死该应用程序 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(0); } catch (Exception e) { } }}
下面开始实现我们的第一个Activity AppStart
。 其实现很简单,就是在指定时间后跳转到我们的主Activity MainActivity
。为了不让用户觉得突兀,在AppStart中使用了简单的动画效果:
public class AppStart extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Activity act = AppManager.getAppManager().getActivity(MainActivity.class); if (null != act && !act.isFinishing()) { finish(); } View rootView = View.inflate(this, R.layout.activity_start, null); setContentView(rootView); // 启动动画 AlphaAnimation alphaAnimation = new AlphaAnimation(0.5f, 1.0f); alphaAnimation.setDuration(800); alphaAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { redirectTo(); } @Override public void onAnimationRepeat(Animation animation) { } }); rootView.startAnimation(alphaAnimation); } public void redirectTo() { Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); }}
MainActivity
是与用户交互的主要页面,它提供了进入其它页面入口。在该类中,主要是4个tabs页和一个侧滑菜单。而个别tabs又会有多个tabs,所以我们就先实现这个基本框架,具体的内容我们后面会逐步的实现。
- 首先创建一个Tabs容类
MyFragmentTabHost
public class MyFragmentTabHost extends FragmentTabHost { private String mCurrentTag; private String mNoTabChangedTag; public MyFragmentTabHost(Context context) { super(context); } public MyFragmentTabHost(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onTabChanged(String tag) { if (tag.equals(mNoTabChangedTag)) { setCurrentTabByTag(mCurrentTag); } else { super.onTabChanged(tag); mCurrentTag = tag; } } public void setNoTabChangedTag(String tag) { this.mNoTabChangedTag = tag; }}
- 紧接着来实现我们的侧滑菜单
NavigationDrawableFragment
。这个类的实现很简单,基本参照了Android官方的教程实现
public class NavigationDrawableFragment extends BaseFragment { private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position"; private NavigationDrawerCallback mCallback; private ActionBarDrawerToggle mDrawerToggle; private DrawerLayout mDrawerLayout; private View mDrawerListView; private View mFragmentContentView; private int mCurrentSelectedPosition = 0; private boolean mFromSavedInstanceState; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION); mFromSavedInstanceState = true; } setSelectItem(mCurrentSelectedPosition); } private void setSelectItem(int position) { mCurrentSelectedPosition = position; if (mDrawerLayout != null) { mDrawerLayout.closeDrawer(mFragmentContentView); } if (mCallback != null) { mCallback.onNavigationDrawerItemSelected(position); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mDrawerListView = inflater.inflate(R.layout.fragment_navigation_drawer, container, false); ButterKnife.bind(this, mDrawerListView); initView(mDrawerListView); initData(); return mDrawerListView; } @Override public void initView(View view) { TextView night = (TextView) view.findViewById(R.id.tv_night); if (AppContext.getNightModeSwitch()) { night.setText("日间"); } else { night.setText("夜间"); } } @Override public void initData() { } /** * 调用者必须使用该方法来设置导航菜单 * * @param fragmentId 该Fragment在它的Activity布局中的id * @param drawerLayout 容纳该Fragment的DrawerLayout对象 */ public void setup(int fragmentId, DrawerLayout drawerLayout) { mFragmentContentView = getActivity().findViewById(fragmentId); mDrawerLayout = drawerLayout; // 在侧边栏菜单打开时,在主内容上设置一个阴影 mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.LEFT); ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); mDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, null, R.string.navigation_drawer_open, R.string.navigation_drawer_close) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); getActivity().invalidateOptionsMenu(); } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); getActivity().invalidateOptionsMenu(); } }; mDrawerLayout.post(new Runnable() { @Override public void run() { mDrawerToggle.syncState(); } }); mDrawerLayout.setDrawerListener(mDrawerToggle); } public interface NavigationDrawerCallback { void onNavigationDrawerItemSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mCallback = (NavigationDrawerCallback) activity; } catch (ClassCastException cce) { throw new ClassCastException("Activity must implement NavigationDrawerCallback"); } } @Override public void onDetach() { super.onDetach(); mCallback = null; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } @OnClick({ R.id.menu_item_quests, R.id.menu_item_opensoft, R.id.menu_item_blog, R.id.menu_item_gitapp , R.id.menu_item_setting, R.id.menu_item_theme , R.id.ll_navi_drawer /* 点击菜单栏其他区域,则关闭菜单栏 */ }) public void onClick(View v) { switch (v.getId()) { case R.id.menu_item_quests: // 技术问答 break; case R.id.menu_item_opensoft: // 开源软件 break; case R.id.menu_item_blog: // 博客区 break; case R.id.menu_item_gitapp: // Git 客户端 break; case R.id.menu_item_setting: // 设置 break; case R.id.menu_item_theme: // 夜间 break; } mDrawerLayout.postDelayed(new Runnable() { @Override public void run() { mDrawerLayout.closeDrawers(); } }, 2000); } private ActionBar getActionBar() { return ((ActionBarActivity) getActivity()).getSupportActionBar(); }}
NavigationDrawableFragment
继承了一个抽象类BaseFragment
,这个类是所有Fragment的基类
public abstract class BaseFragment extends Fragment implements BaseFragmentInterface { @Override public abstract void initView(View view); @Override public abstract void initData();}
我们已经有了Tabs容器和侧滑菜单了,接下来就是实现如何提供我们Tabs。在这里,我们使用一个枚举类MainTab
来表明每个Tab
public enum MainTab { NEWS(0, R.string.main_tab_name_news, R.drawable.tab_icon_new, NewsViewPagerFragment.class), TWEET(1, R.string.main_tab_name_tweet, R.drawable.tab_icon_tweet, TweetsViewPagerFragment.class), QUICK(2, R.string.main_tab_name_quick, R.drawable.tab_icon_new, null), EXPLORE(3, R.string.main_tab_name_explore, R.drawable.tab_icon_explore, ExploreFragment.class), ME(4, R.string.main_tab_name_my, R.drawable.tab_icon_me, MyInformationFragment.class); private int idx; private int resName; private int resIcon; private Class<?> clz; /** * @param idx 在TabHost中的位置,从0开始 * @param resName Tab的名称 * @param resIcon Tab的icon * @param clz 点击Tab所显示的内容 */ private MainTab(int idx, int resName, int resIcon, Class<?> clz) { this.idx = idx; this.resName = resName; this.resIcon = resIcon; this.clz = clz; } public int getIdx() { return idx; } public void setIdx(int idx) { this.idx = idx; } public int getResName() { return resName; } public void setResName(int resName) { this.resName = resName; } public int getResIcon() { return resIcon; } public void setResIcon(int resIcon) { this.resIcon = resIcon; } public Class<?> getClz() { return clz; } public void setClz(Class<?> clz) { this.clz = clz; }}
为了确保应用能运行,我们还需要创建几个Fragment
- 资讯viewpager页面
NewsViewPagerFragment
,现在我们先不实现ViewPagerFragment,只实现基本的框架
public class NewsViewPagerFragment extends BaseViewPagerFragment {}
其基类BaseViewPagerFragment
也很简单
public class BaseViewPagerFragment extends BaseFragment { @Override public void initView(View view) { } @Override public void initData() { }}
- 动弹界面
TweetsViewPagerFragment
(包括最新动弹、热门动弹、我的动弹)
public class TweetsViewPagerFragment extends BaseViewPagerFragment {}
- 发现页面
ExploreFragment
public class ExploreFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View view = inflater.inflate(R.layout.fragment_explore, null); initView(view); ButterKnife.bind(this, view); return view; } @Override public void initView(View view) { } @Override public void initData() { }}
- 登录用户中心页面
MyInformationFragment
public class MyInformationFragment extends BaseFragment { @Override public void initView(View view) { } @Override public void initData() { }}
现在就差我们今天的主角了MainActivity
,其实现很简单,就是将TabHost和侧滑菜单初始化
public class MainActivity extends ActionBarActivity implements NavigationDrawableFragment.NavigationDrawerCallback, BaseViewInterface, View.OnClickListener, TabHost.OnTabChangeListener { private NavigationDrawableFragment mNavigationDrawableFragment; @Bind(android.R.id.tabhost) MyFragmentTabHost mTabHost; private CharSequence mTitle; @Bind(R.id.quick_option_iv) View mQuickOption; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); initView(); AppManager.getAppManager().addActivity(this); } @Override public void onNavigationDrawerItemSelected(int position) { } @Override public void initView() { mNavigationDrawableFragment = (NavigationDrawableFragment) getSupportFragmentManager().findFragmentById( R.id.navigation_drawer); mNavigationDrawableFragment.setup(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); mTitle = getTitle(); mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent); mTabHost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); initTabs(); mQuickOption.setOnClickListener(this); mTabHost.setCurrentTab(0); mTabHost.setOnTabChangedListener(this); } private void initTabs() { MainTab[] tabs = MainTab.values(); int size = tabs.length; for (int i = 0; i < size; i++) { MainTab mainTab = tabs[i]; TabHost.TabSpec tab = mTabHost.newTabSpec(getString(mainTab.getResName())); View indicator = View.inflate(this, R.layout.tab_indicator, null); TextView title = (TextView) indicator.findViewById(R.id.tab_title); Drawable icon = getResources().getDrawable(mainTab.getResIcon()); title.setCompoundDrawablesWithIntrinsicBounds(null, icon, null, null); if (i == 2) { // 快速操作按钮没有Tab内容 indicator.setVisibility(View.INVISIBLE); mTabHost.setNoTabChangedTag(getString(mainTab.getResName())); } title.setText(getString(mainTab.getResName())); tab.setIndicator(indicator); tab.setContent(new TabHost.TabContentFactory() { @Override public View createTabContent(String tag) { return new View(MainActivity.this); } }); mTabHost.addTab(tab, mainTab.getClz(), null); } } @Override public void initData() { } @Override public void onClick(View v) { } @Override public void onTabChanged(String tabId) { int size = mTabHost.getTabWidget().getTabCount(); for (int i = 0; i < size; i++) { View v = mTabHost.getTabWidget().getChildAt(i); if (i == mTabHost.getCurrentTab()) { v.setSelected(true); } else { v.setSelected(false); } } }}
效果图如下
好了,基本框架我们已经搭建完成了,后面我们会逐渐让其丰富起来…
源码下载
commit 版本: c3a27c
- 如此抄袭Apps之OscHub(一)
- 如此抄袭Apps之OscHub(二)
- 如此抄袭Apps之OscHub(三)
- Mac抄袭施乐PARC图形界面?果真如此?还真是如此啊!
- 抄袭之作,用一下.
- 生命如此之轻
- 经济危机如此之近
- 如此之贱
- iTeXmacs如此之慢
- 《泰囧》如此之火
- 天下如此之大
- 天下如此之大
- 今天如此之忙
- 房价为何如此之高
- grep为何如此之快
- 为何编程如此之难?
- 对一稿多用与抄袭的看法
- 《网络黑白》一书所抄袭的文章列表
- 关于SharedPreferences中无法改变Set的问题。
- Cardboard虚拟现实开发初步(三)
- Struts2 注解中跳转 action
- C程序的编译和链接
- 剑指Offer面试题18(Java版):树的子结构
- 如此抄袭Apps之OscHub(一)
- HttpClient使用详解
- MAT使用及OOM分析
- 3Sum Closest
- MeasureSpec学习
- JSP和El表达式和JSTL标签库使用
- MongoDB初探系列之四:MongoDB与Java共舞
- Reverse Words in a String
- 7月份,一个多月来的总结