抱歉,你查看的文章不存在

直播项目之仿全民TV(已开源)

前言:成印昨天发布了他最近闭关10天,开发的一个直播开源项目,我体验了下,觉得还不错,推荐给大家,点击文末【阅读原文】,可访问项目的github地址,觉得不错,也不忘给他star下,毕竟开源不易。下面是他的来稿。ps:(最近我正在参加CSDN 2016年度博客之星评选, 11月28日到12月18日每天都可以为我投一票 ,投票地址前篇文章中【阅读原文】就有,我朋友圈也有相关分享,也可直接链接:http://blog.csdn.net/vote/candidate.html?username=hejjunlin。)

直播一直是个很火的趋势,特别是在今年以来让不少平凡的草根百姓也和狠狠“火”了一把,什么某游戏主播年薪一千五百万,某女主播直播吃饼干,吃饭,月入十万,等等。。但这些跟我们没有半点关系。但都没有关系,代码还能愉快的敲下去,砖还得搬,搬还得钻。也是抱着学习的态度和对技术的热诚,动动手写下这项目,然后分享给大家。先看下今天的Agenda:

  1. 树形结构的架构思路
  2. 模块化的开放方法
  3. 总结

1.树形结构的架构思路

很多有经验的开发人员,在一定的项目洗礼之后,逐渐形成一套可行的敏捷开发的方案,就是俗话中的套路。本人也有一套路,命称树形套路。在这里阐述的所谓树形,是从项目出发,以数据走向到每个功能点、页面的发散型项目观,在树形中的每个节点,可以按照个人的特点以为一个Activity、Fragment等作为一个节点,Intent、FragmentAdapter作为连接节点之间的纽带。 再细化,将Activity、Fragment作为一个树根发散来看待项目架构。最终,在树形项目架构中,便会产生1级、2级、3级节点出来 。 在同一级别的节点,有共同点,使用模板设计模式从中抽取。

以本项目中的推荐页来说

从功能层面看到 tab 精彩推荐和 tab 颜值控、英雄联盟等属于第一节点级别。但是精彩推荐和其他tab下的效果有很大的差别。而其他tab下的都是列表形式。那么精彩推荐可以单独分离一个Fragment,其他Tab可以抽象出一个BaseLiveWraperFragment。在这些BaseLiveWraperFragment中,其中颜值控的页面每个Item显示和其他不同。那么以LoveLiveListFragment作为父类,将数据绑定和Item布局的设定延迟到具体的子类LoveLiveListFragment中,实现子列表的独特展现。示例代码:

public class LoveLiveListFragment extends BaseLiveWraperFragment {
    private LiveInteractor mLiveInteractor;
    private int mScreenWidth;
 public static LoveLiveListFragment newInstance(Bundle args) {
     LoveLiveListFragment fragment = new LoveLiveListFragment();
     fragment.setArguments(args);
     return fragment;
 }
 @Override
 public int getListItemLayout() {
     return R.layout.listitem_love; //item 布局
 }
 @Override
 protected void convertItem(ViewHolder holder, final PlayBean playBean, int position) { //item数据绑定
     holder.setImageUrl(R.id.thumnails,playBean.thumb,new GlideRoundTransform(mActivity,5));
     holder.setText(R.id.tv_viewnum,playBean.view);
     holder.setText(R.id.intro,playBean.title);
     ..省略数行.
 }
 @Override
 public void getDataError(String errmsg) {
     showToast("获取颜值控数据失败");
 }
}

父类BaseLiveWraperFragment相关方法:

public class BaseLiveWraperFragment extends BaseListFragment<PlayBean> {
 protected String mUrl;
 private LiveInteractor mLiveInteractor;
 private String mTag;
 public static BaseLiveWraperFragment newInstance(Bundle args) {
     BaseLiveWraperFragment fragment = new BaseLiveWraperFragment();
     fragment.setArguments(args);
     return fragment;
 }
 public int getListItemLayout() { //item 布局
     return R.layout.listitem_live;
 }
 @Override
 protected void convertItem(ViewHolder holder, final PlayBean playBean, int position) { //item数据绑定
     holder.setImageUrl(R.id.thumnails,playBean.thumb,new GlideRoundTransform(mActivity,5));
     holder.setText(R.id.title,playBean.title);
     ..省略数行.
 }
 @Override
 protected void initData() {  //获取网络数据
     Bundle arguments = getArguments();
     mUrl = arguments.getString("url", "");
     mTag = arguments.getString("tag", "");
     mLiveInteractor = new LiveInteractor();
     mLiveInteractor.loadPlayList(this,mUrl);
 }
 @Override
 public void getDataError(String errmsg) {
     showToast("获取"+mTag+"数据失败");
 }
}

而以BaseListFragment为跟节点,很多列表的实现都以此延伸,从然产生另一个“树”。这样,咱们可以将大大提高了代码了重用性、拓展性。结构也清晰明了。 对这一块有相同的开发思路就是对Presenter的处理是类似的,这里不深探究。

2. 模块化的开放方法

如果说树形方法是纵向的开发思路。那么模块化的开发方法就是横向的实现方式。在每一个节点中,以Activity为例,一个Activity所包含的功能中,可以想象为若干的Part组成。Part可以是功能或者View都可以。以项目中的直播页面为示例:

就目前的CommonLiveUI中:本人将Part分为:

  1. 播放器
  2. 竖屏控件持有者
  3. 横屏控件持有者

在onCreate方法中对其初始化操作,完了就放养了

@Override
protected void onCreate(Bundle savedInstanceState) {
           getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    super.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.act_commonliveplayer);
    //1.初始化dagger2
    mActivityComponent.inject(this);
    mBasePresent=livePlayerPresenterImpl;
    livePlayerPresenterImpl.attachView(this);
    initPlayer();  //2.初始化播放器
    initVerControll();//3.初始化竖屏控件持有者
    initHorContrll();//4.初始化横屏控件持有者
    initData();//5.初始化数据
}

初始化完,让各回各家,各找各妈。结合mvp+dagger2下来,优雅的解耦。而其余的代码,就是各种回调,刷新UI等等。

 protected void onResume() {
     super.onResume();
     if (playerHolder!=null)
     playerHolder.onResume();
 }
 @Override
 protected void onPause() {
     super.onPause();
     if (playerHolder!=null)
     playerHolder.onPause();
 }
 @Override
 protected void onDestroy() {
     if (playerHolder!=null){
         playerHolder.release();
         playerHolder=null;
     }
     verticalControll.onDestroy();
     horizontalControll.onDestroy();
     super.onDestroy();
 }
 @Override
 protected void toPrepare() {
     if (playerHolder!=null)
     playerHolder.prepare();
 }
 @Override
 public void onConfigurationChanged(Configuration newConfig) {
     super.onConfigurationChanged(newConfig);
     if (getRequestedOrientation()==ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
         //portrait
     }else {
         //landscape
     }
 }
 @Override
 public void setRequestedOrientation(int requestedOrientation) {
     super.setRequestedOrientation(requestedOrientation);
     if (requestedOrientation==ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
         isVertical=true;
         verticalControll.onCreate();
         horizontalControll.onDestroy();
     }else {
         isVertical=false;
         horizontalControll.onCreate();
         verticalControll.onDestroy();
     }
 }
 @Override
 public void onConnecting() {
     mLoadingView.setVisibility(View.VISIBLE);
 }
 @Override
 public void onReConnecting() {
     showToastTips("正在重连...");
 }
 @Override
 public void onConnectSucces() {
     mLoadingView.setVisibility(View.GONE);
 }
 @Override
 public void onConnectFailed() {
     showToastTips("连接失败");
 }
 @Override
 public void onPlayComleted() {
     showToastTips("主播离开了");
 }
 @Override
 public void onPlayerStart() {
     bgImage.animate().alpha(0).setDuration(1000).start();
 }
 @Override
 public void onPlayePause() {
 }
 @Override
 public void onRoomData(JSONObject roomJson) {
     mRoomDataController = new RoomDataController(roomJson);
     mPlayerPath = mRoomDataController.getPlayerPath(0);
     playerHolder = new LivePlayerHolder(this,mSurfaceView,mCodec,mPlayerPath);
     playerHolder.startPlayer();
 }
 @Override
 public void onBackPressed() {
     if (getRequestedOrientation()==ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         ViewGroup.LayoutParams params =
                 (ViewGroup.LayoutParams) mSurfaceView.getLayoutParams();
         params.width=mPortWidth;
         params.height=mPortHeight;
         ViewGroup.LayoutParams mStatusbarParams = mStatusbar.getLayoutParams();
         mStatusbarParams.height= (int) (getResources().getDimension(R.dimen.status_bar_height)+0.5f);
         getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
         return;
     }
     if (playerHolder!=null)
         playerHolder.release();
     super.onBackPressed();
 }
 @Override
 public void onVerticalClickFullScreen() {
     setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
     Display display =
             getWindow().getWindowManager().getDefaultDisplay();
     DisplayMetrics metrics = new DisplayMetrics();
     display.getMetrics(metrics);
     int heightPixels = metrics.heightPixels;
     int widthPixels = metrics.widthPixels;
     Log.e("metrics","heightPixels"+heightPixels);
     Log.e("metrics","widthPixels"+widthPixels);
     ViewGroup.LayoutParams params =
             (ViewGroup.LayoutParams) mSurfaceView.getLayoutParams();
     int height = params.height;
     int width = params.width;
     //status bar
     ViewGroup.LayoutParams mStatusbarParams = mStatusbar.getLayoutParams();
     mStatusbarParams.height=0;
     getWindow().getDecorView().setSystemUiVisibility(View.INVISIBLE);
     Log.e("mSurfaceView","width"+width);
     Log.e("mSurfaceView","height"+height);
     mPortWidth=width;
     mPortHeight=height;
     params.width=widthPixels;
     params.height=heightPixels;
 }
 @Override
 public boolean onTouch(View v, MotionEvent event) {
     LogUtil.i("TOUCH  "+isVertical);
     verticalControll.onTouchEvent(isVertical,event);
     horizontalControll.onTouchEvent(isVertical,event);
     return false;
 }
}

代码不超过300行。以此完成功能的模块化,非常直观的阅读。这样,少挖点坑,为后来接手的人减少点酷刑。

不过值得一提的是,在各种“持有者” 中,最好类似生命周期onCreate()和onDestory()方法,方便做资源释放,减少内存泄漏的发生。

部分截图:

横屏:

项目地址:https://github.com/a371166028/likequanmintv

原文发布于微信公众号 - 何俊林(DriodDeveloper)

原文发表时间:2016-12-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

码农突围

0 篇文章93 人订阅

相关文章

来自专栏Android群英传

不需要再手写 onSaveInstanceState 了,因为你的时间非常值钱

9510
来自专栏Android开发指南

11.菜单界面、详情界面

30370
来自专栏Winter漫聊技术

Android : 四行代码,优雅返回

为了防止用户误触返回键,还在使用 “再按一次退出” 吗? 追求简约与极速的时代,这种交互显然已经Out了嘛~

15620
来自专栏developerHaoz 的安卓之旅

手把手教你从零开始做一个好看的 APP - Day four

本文为 手把手教你从零开始做一个好看的 APP - Day four ,如果想看该系列的其他文章,请点击以下连接

9620
来自专栏技术小黑屋

纠结才能写出好代码

程序员的代码修炼应该有两个目标,一个是代码的执行效率,另一个是代码的可读性。朝着这两个目标努力的人很多,但是能够达到目标的人很少。

9410
来自专栏开发之途

Android 自定义Wifi信号指示View

18750
来自专栏向治洪

android来电归属地提醒

现在市面上常用的一些拨号软件的一个功能,来电归属地。拨号的时候,会在拨号界面出现一个号码归属地的小框框。效果如下:而且这个小窗体还可以自定义风格,并且可以自由移...

26370
来自专栏增长技术

App Intro相关

##How to use Add this to your build.gradle:

8220
来自专栏Phoenix的Android之旅

关于ListView的那些坑

做过Android的同学应该都有适配安卓兼容性的问题,今天我们来说一个常见却又经常被忽略的问题。

7810
来自专栏函数式编程语言及工具

Akka(31): Http:High-Level-Api,Route rejection handling

   Route 是Akka-http routing DSL的核心部分,使用户能比较方便的从http-server的角度筛选http-request、进行se...

26170

扫码关注云+社区

领取腾讯云代金券