Android高效的EPG界面实现方式

                                           Android高效的EPG界面实现方式

       你现在没有在为怎么去实现EPG界面而烦恼呢?仔细研究下我这篇博客后,肯定会对你有很大的帮助的。我这实现后的效果最大的特点就是UI响应速度很快(上下左右切换时,UI能很快的刷新数据)。

       首先看下效果图:

       最开始进入EPG界面时,焦点在Genres列表里,按右键后,整个界面会往左滑动,直到Genres列表完全消失,然后把焦点落在program上。

       上面的UI是用Fragment的方式实现的,UI布局看下面的XML相信你很快能明白。需要注意的就是红色框框里layout_width的值,它是全屏宽度+group listview的宽度。上面说的向左滑动时,滑动的距离就是group listview的宽度。

        加载EPG数据,可以有两种方式:

        1)把所有channel的EPG一次性加载到内存里。这样做的优点是:用户进行操作时,不需要再去做数据的加载动作,毕竟要耗时的,只需要从内存里取数据,响应比较及时;缺点就是:当EPG数据比较多时,有的时候数据有40M左右,这样就很占用内存,对于内存比较小的机器就会有很大的影响。

        2)始终只加载当前显示的那些节目的EPG,当然如果内存中有了就不用去加载了。这种做法的优点很明显了,节省了大量内存;缺点就是:对于用户的操作,有些情况需要再次去加载EPG数据,UI上的数据刷新会有点延后。

        我采用的是第2种方法,从实际效果看,不影响体验感!具体逻辑如下:

        1)所有频道的节目信息保存在内存里,由ProgramsDataManager管理。

        2)初次显示EPG主界面时,从数据库里加载当前可见频道当天的EPG信息,并保存到内存。

        3)上下切换频道时,切换动作停止后,如果内存里有当前可见频道从当天0点时刻到当前页天的24点时刻的EPG信息就直接显示,否则从数据库里读取EPG数据到内存,然后显示。

        4)上下切换频道时,当前页的时间可能是后续某天时间,切换停住后,如果这些天的EPG在内存里都有,直接显示;否则会清除这些可见频道已经存在内存里的EPG数据,然后再从数据里读取这些可见频道这些天的EPG到内存,然后显示。

        5)day+时,如果内存里已经存有了可见频道day+的EPG数据,直接显示就行了。否则会清除这些可见频道已经存在内存里的EPG数据,然后从数据库里读取可见频道从当天0点时刻到day+天24点之间的EPG数据到内存,然后显示。

        6)day-时,按照day+的逻辑,实际上要显示的数据已经在内存里有了,直接显示就行了。

        7)为了节省内存,每当EPG主界面退出后,会把内存里的EPG数据清除掉。

 

     下面介绍主要代码

        EpgMenuFragment相关代码:

    //相关变量
    private static final int UI_MSG_GROUP_LIST_GET_FOCUS = 101;
    private static final int UI_MSG_REFRESH_TIME_LINE = 102;
    private static final int UI_MSG_REDRAW_EGPVIEW = 103;
    private static final int UI_MSG_UPDATE_GROUP_CHANNEL_DATA = 104;
    private static final int UI_MSG_SHOW_NEXT_DAY_EPG = 105;
    private static final int UI_MSG_SHOW_PREV_DAY_EPG = 106;

    private static final int SUB_MSG_LOAD_CHANNEL_DATA = 1001;
    private static final int SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA = 1002;

    private IChannelDataManager mChannelDataManager;
    private FavoriteManager mFavoriteManager;
    private DvrManager dvrManager;
    private MonitorProgramHelper monitorProgramHelper;

    public boolean mHidden = true;
    public ObjectAnimator mTransXAnimator;
    private View root_view;
    private LinearLayout genresLinearLayout;
    private LinearLayout mLlupper;

    private ListView mGroupListView;
    private EpgGroupListAdapter mGroupListAdapter = null;
    private ListView mChannelListView;
    private ChannelListAdapter mChannelListAdapter = null;

    private int lastFirstItemViewBottom;
    private boolean channelListFirstItemScreenLocationChanged = false;

    private ObjectAnimator animator1;
    private ObjectAnimator animator2;

    private EpgView mEpgView;
    private TextView curDataWeek;

    //存放当前组的channel
    private ArrayList<Channel> mCurrentGroupChannels;

    public ArrayList<ScheduleRecording> mScheduleData;  // timer数据
    private ArrayList<EpgChannelGroup> mAllGroupList = new ArrayList<>();

    private HandlerThread subThread = new HandlerThread("SubThread");
    private Handler mSubHandler;
    private boolean mChangedGroup = false;

    private int mCurrentGroupPosition = 0;
    private int mLastGroupPosition = 0;
    private EpgChannelGroup mCurrentGroupInfo;

    //当前播放节目的id,该id的值在tv.db数据库中
    private long mCurrentChannelId = 0;
    //节目播放后记录该节目的索引位置
    private int mLastChannelPosition = 0;
    private long left_key_interval = 0;
    private TvClock mTvClock;

    public static EpgMenuFragment newInstance() {
        EpgMenuFragment epgMenuFragment = new EpgMenuFragment();
        return epgMenuFragment;
    } 


    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);

        startSubThread();
    }

    private void startSubThread() {
        subThread.start();

        mSubHandler = new Handler(subThread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case SUB_MSG_LOAD_CHANNEL_DATA:
                        //加载某个组的channel
                        Log.d(TAG, "receive SUB_MSG_LOAD_CHANNEL_DATA");
                        EpgChannelGroup channel_group = (EpgChannelGroup) msg.obj;
                        loadChannelDataByGroup(channel_group);
                        mEpgMenuUIHandler.sendEmptyMessage(UI_MSG_UPDATE_GROUP_CHANNEL_DATA);
                        break;

                    case SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA:
                        //加载索引从index开始的channels的EPG信息
                        int index = msg.arg1;
                        int needClearData = msg.arg2;
                        Log.d(TAG, "receive SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA, index = " + index + ", needClearData = " + needClearData);

                        //今天的凌晨0点时刻,即查询的起始时间
                        long queryStartTime = EPGUtils.getDayStartTime(mEpgView.getTimeOffset());

                        //当前页所属天的凌晨0点时刻
                        long firstOfCurrentPageDay = EPGUtils.getDayStartTime(mEpgView.getTimeLowerBoundary());
                        //Log.d("wujiang", "receive SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA, firstOfCurrentPageDay = " + firstOfCurrentPageDay);

                        //当前页所属天的晚上24点时刻
                        long endOfCurrentPageDay = firstOfCurrentPageDay + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;
                        //Log.d("wujiang", "receive SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA, endOfCurrentPageDay = " + endOfCurrentPageDay);

                        loadChannelsProgramFromDatabase(index, queryStartTime, endOfCurrentPageDay, needClearData);
                        break;
                }
            }
        };
    }

    /**
     * 从数据库里加载当前页节目的EPG信息
     * @param start_channel_index
     */
    private void loadChannelsProgramFromDatabase(int start_channel_index, long start_time, long end_time, int needClearData) {
        ArrayList<Long> queryChannelsArrayList = new ArrayList<>();

        //Log.d("wujiang", "loadChannelsProgramFromDatabase: start_time = " + TimeUtil.millis2String(start_time));
        //Log.d("wujiang", "loadChannelsProgramFromDatabase: end_time = " + TimeUtil.millis2String(end_time));

        int current_index = start_channel_index;
        int count;

        for (count = 0; current_index < mCurrentGroupChannels.size() && count < EpgView.MaxItemCountInOnePage + 1; count++, current_index++) {
            queryChannelsArrayList.add(mCurrentGroupChannels.get(current_index).getId());
            //Log.d("wujiang", "loadChannelsProgramFromDatabase: channel id = " + mCurrentGroupChannels.get(current_index).getId());
            //Log.d("wujiang", "loadChannelsProgramFromDatabase: channel name = " + mCurrentGroupChannels.get(current_index).getDisplayName());
        }

        ProgramsDataManager.getInstance().queryChannelsProgramFromDatabase(start_time, end_time, queryChannelsArrayList, needClearData);
    }

    //得到某个组下面的channel,根据自己的情况来实现
    private void loadChannelDataByGroup(EpgChannelGroup channelGroup) {
        mCurrentGroupChannels.clear();
        mCurrentGroupChannels.addAll(((TvActivity) getActivity()).getChannelDataManager().filterChannelList(channelGroup.getType(), channelGroup.getId()));
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
        initView();
    }

    private void initData() {
        mHidden = false;

        mAlarmDataManager = (AlarmDataManager) DataFactory.get().getManager(TvApplication.getAppContext(), DataFactory.ALARM_MANAGER);
        mChannelDataManager = (IChannelDataManager) DataFactory.get().getManager(TvApplication.getAppContext(), DataFactory.CHANNEL_MANAGER);
        mFavoriteManager = ((FavoriteManager) DataFactory.get().getManager(TvApplication.getAppContext(), DataFactory.FAVORITE_MANAGER));
        dvrManager = (DvrManager) DataFactory.get().getManager(TvApplication.getAppContext(), DataFactory.DVR_MANAGER);
        ProgramsDataManager.getInstance().addListener(queryFinishListener);
        mTvClock = new TvClock(getContext());

        if (mCurrentGroupChannels == null) {
            mCurrentGroupChannels = new ArrayList<>();
        }

        getAllGroups();

        //显示ALL组的节目
        mLastGroupPosition = mCurrentGroupPosition = FilterType.ALL;

        if (mGroupListAdapter != null) {
            mGroupListAdapter.setCurrentGroupPosition(mCurrentGroupPosition);
            mGroupListAdapter.setCurrentGroupInfo(mAllGroupList.get(FilterType.ALL));
            mGroupListAdapter.notifyDataSetChanged();
            mGroupListView.setSelection(0);
        }
    }

    //得到所有Group的数据(根据自己平台情况实现)
    private void getAllGroups() {
        if (mAllGroupList != null) {
            mAllGroupList.clear();
        } else {
            mAllGroupList = new ArrayList<>();
        }

        mAllGroupList.add(new EpgChannelGroup("All", -1, FilterType.ALL));
        mAllGroupList.add();...
    }

    private void initView() {
        root_view = getView().findViewById(R.id.root_view);
        genresLinearLayout = getView().findViewById(R.id.ll_favourite_group);
        mLlupper = getView().findViewById(R.id.ll_upper);
        curDataWeek = getView().findViewById(R.id.cur_data_week);
        mEpgView = getView().findViewById(R.id.epg_view);

        findGroupListView();
        findChannelListView();

        root_view.postDelayed(new Runnable() {
            @Override
            public void run() {
                mGroupListView.requestFocus();
            }
        }, 300);

        mEpgMenuUIHandler.sendEmptyMessageDelayed(UI_MSG_REFRESH_TIME_LINE, 60 * 1000);
    }

    private void findGroupListView() {
        mGroupListView = getView().findViewById(R.id.lv_group_list);
        mGroupListAdapter = new EpgGroupListAdapter(getContext(), mAllGroupList);
        mGroupListAdapter.setCurrentGroupPosition(FilterType.ALL);
        mGroupListAdapter.setCurrentGroupInfo(mAllGroupList.get(FilterType.ALL));
        mGroupListView.setAdapter(mGroupListAdapter);


        mGroupListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                setCurrentGroupPosition(position);
                setCurrentGroupInfo(mGroupListAdapter.getItem(position));
                setGroupListViewBackground(mLastGroupPosition, parent);
                setGroupListViewBackground(position, parent);
                mLastGroupPosition = position;

                mSubHandler.removeMessages(SUB_MSG_LOAD_CHANNEL_DATA);
                Message msg = mSubHandler.obtainMessage();
                msg.what = SUB_MSG_LOAD_CHANNEL_DATA;
                msg.obj = mGroupListAdapter.getItem(position);
                mSubHandler.sendMessageDelayed(msg, 300);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        mGroupListView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean focus) {
                if (focus) {
                    genresLinearLayout.post(new Runnable() {
                        @Override
                        public void run() {
                            float TranslationX = mLlupper.getTranslationX();
                            float end_position = 0;

                            animator1 = ObjectAnimator.ofFloat(mLlupper, "translationX", TranslationX, end_position);
                            animator1.setDuration(300);
                            animator1.start();
                        }
                    });

                } else {
                    genresLinearLayout.post(new Runnable() {
                        @Override
                        public void run() {
                            float TranslationX = mLlupper.getTranslationX();
                            float end_position = TranslationX - genresLinearLayout.getWidth() - getResources().getDimensionPixelSize(R.dimen.listviews_space);

                            animator2 = ObjectAnimator.ofFloat(mLlupper, "translationX", TranslationX, end_position);
                            animator2.setDuration(300);
                            animator2.start();
                        }
                    });
                }
            }
        });
    }

    public void setCurrentGroupPosition(int group_position) {
        mCurrentGroupPosition = group_position;
        if (mGroupListAdapter != null) {
            mGroupListAdapter.setCurrentGroupPosition(group_position);
        }
    }

    public void setCurrentGroupInfo(EpgChannelGroup epgChannelGroup) {
        mCurrentGroupInfo = epgChannelGroup;
        Log.d(TAG, "setCurrentGroupInfo: mCurrentGroupInfo name = " + mCurrentGroupInfo.getName());
        if (mGroupListAdapter != null) {
            mGroupListAdapter.setCurrentGroupInfo(epgChannelGroup);
        }
    }

    private void setGroupListViewBackground(int position, AdapterView<?> parent) {
        if (parent.getFirstVisiblePosition() <= position && position <= parent.getLastVisiblePosition()) {
            View convertView = parent.getChildAt(position - parent.getFirstVisiblePosition());
            if (position >= 0 && mGroupListAdapter.getCount() > position && mCurrentGroupPosition == position) {
                convertView.setBackgroundResource(R.drawable.sel_epg_group_list_selected);
            } else {
                convertView.setBackgroundResource(R.drawable.sel_epg_group_list_normal);
            }
        }
    }

    private void findChannelListView() {
        mChannelListView = getView().findViewById(R.id.lv_channel_list);

        mChannelListView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
                if (KeyEvent.ACTION_DOWN != keyEvent.getAction()) {
                    return false;
                }

                switch (keyCode) {
                    case KeyEvent.KEYCODE_PAGE_DOWN:
                        KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN);
                        mChannelListView.dispatchKeyEvent(downEvent);
                        return true;

                    case KeyEvent.KEYCODE_PAGE_UP:
                        KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP);
                        mChannelListView.dispatchKeyEvent(upEvent);
                        return true;

                    case KeyEvent.KEYCODE_DPAD_UP:
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        int current_position = mChannelListView.getSelectedItemPosition();
                        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                            if (current_position == 0) {
                                mChannelListView.setSelection(mChannelListView.getCount() - 1);
                                return true;
                            }
                        } else {
                            if (current_position == (mChannelListView.getCount() - 1)) {
                                mChannelListView.setSelection(0);
                                return true;
                            }
                        }
                        break;
                }

                return false;
            }
        });

        mChannelListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long l) {
                long current_time = mTvClock.currentTimeMillis();

                if (mEpgView.currentProgram == null) {
                    return;
                }

                long start_time = mEpgView.currentProgram.getStartTimeUtcMillis();
                //Log.d(TAG, "mChannelListView's onItemClick, start_time = " + start_time);

                if (start_time > current_time) {
                    //显示EPG的详情信息
                    showDetailDialogFullWindowDialog(mEpgView.currentProgram);
                    return;
                }

                Channel channel = mChannelListAdapter.getItem(position);
                setCurrentChannelId(channel.getId());
                //Log.d("wujiang", "mChannelListView's onItemClick, 1 mLastChannelPosition = " + mLastChannelPosition);
                setChannelListViewBackground(mLastChannelPosition, parent);
                setChannelListViewBackground(position, parent);
                mLastChannelPosition = position;
                //Log.d("wujiang", "mChannelListView's onItemClick, 2 mLastChannelPosition = " + mLastChannelPosition);
                //去播放channel
                ((TvActivity) getActivity()).checkNeedStopDvrWhenTuneChannel(channel);
                ((TvActivity) getActivity()).getNesFragmentManager().hideAll();
            }
        });


        mChannelListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
                Log.d(TAG, "mChannelListView's onItemSelected");

                mEpgMenuUIHandler.removeMessages(UI_MSG_REDRAW_EGPVIEW);

                if (mEpgView.currentChannelPosition >= 0) {
                    if (mChannelListView.isFocused()) {
                        mEpgView.setDrawNeedSelect(true);
                    }

                    if (position > mEpgView.currentChannelPosition) {
                        mEpgView.moveToDestPosition(position, EpgView.DOWN_DIRECTION);
                    } else if (position < mEpgView.currentChannelPosition) {
                        mEpgView.moveToDestPosition(position, EpgView.UP_DIRECTION);
                    }
                }

                //如果刚刚是切换组了,就不要再去加载EPG,因为切组那里会去查询当前页节目的EPG数据。
                if (mChangedGroup) {
                    mChangedGroup = false;
                    Log.d(TAG, "mChannelListView's onItemSelected: 刚刚已经切换组了,现在不需要重新去加载EPG。");
                    return;
                }

                mSubHandler.removeMessages(SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA);

                //去数据里查询当前页节目的EPG数据
                Message obtainMessage = mSubHandler.obtainMessage();
                obtainMessage.what = SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA;
                obtainMessage.arg1 = mChannelListView.getFirstVisiblePosition();
                obtainMessage.arg2 = 0;
                mSubHandler.sendMessageDelayed(obtainMessage, 500);
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

        mChannelListView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean focus) {
                Log.d(TAG, "mChannelListView's onFocusChange: focus = " + focus);
                if (focus) {
                    mEpgView.setDrawNeedSelect(true);
                    mEpgMenuUIHandler.sendEmptyMessageDelayed(UI_MSG_REDRAW_EGPVIEW, 200);
                } else {
                    mEpgView.setDrawNeedSelect(false);
                }
            }
        });

        mChannelListAdapter = new ChannelListAdapter(getContext(), mCurrentGroupChannels);
        mChannelListView.setAdapter(mChannelListAdapter);

        if (mGroupListAdapter.getCount() > 0) {
            mSubHandler.removeMessages(SUB_MSG_LOAD_CHANNEL_DATA);
            Message msg = mSubHandler.obtainMessage();
            msg.what = SUB_MSG_LOAD_CHANNEL_DATA;
            msg.obj = mGroupListAdapter.getItem(0);
            mSubHandler.sendMessageDelayed(msg, 30);
        }
    }

    public void setCurrentChannelId(long channelId) {
        mCurrentChannelId = channelId;
        if (mChannelListAdapter != null) {
            mChannelListAdapter.setCurrentChannelId(channelId);
        }
    }

    private void setChannelListViewBackground(int position, AdapterView<?> parent) {
        //Log.d("wujiang", "setChannelListViewBackground: position = " + position);
        //Log.d("wujiang", "setChannelListViewBackground: getFirstVisiblePosition = " + parent.getFirstVisiblePosition());
        //Log.d("wujiang", "setChannelListViewBackground: getLastVisiblePosition = " + parent.getLastVisiblePosition());
        if (parent.getFirstVisiblePosition() <= position && position <= parent.getLastVisiblePosition()) {
            View convertView = parent.getChildAt(position - parent.getFirstVisiblePosition());

            if ((position >= 0)
                    && (mChannelListAdapter.getCount() > position)
                    && (mCurrentChannelId == mChannelListAdapter.getItem(position).getId())) {
                //Log.d("wujiang", "setChannelListViewBackground 1111 selected");
                convertView.setBackgroundResource(R.drawable.sel_epg_channel_list_selected);
            } else {
                //Log.d("wujiang", "setChannelListViewBackground 2222 normal");
                convertView.setBackgroundResource(R.drawable.sel_epg_channel_list_normal);
            }
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        mEpgMenuUIHandler.removeCallbacksAndMessages(null);
        mSubHandler.removeCallbacksAndMessages(null);
        mSubHandler.getLooper().quit();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        mHidden = hidden;

        if (hidden) {
            mEpgMenuUIHandler.removeCallbacksAndMessages(null);
            ProgramsDataManager.getInstance().removeListener(queryFinishListener);

            if (mCurrentGroupChannels != null) {
                mCurrentGroupChannels.clear();
            }

            ProgramsDataManager.getInstance().clearData();
            monitorProgramHelper.stop();
        } else {
            initData();
            mGroupListAdapter.setData(mAllGroupList);

            if (mGroupListAdapter.getCount() > 0) {
                mSubHandler.removeMessages(SUB_MSG_LOAD_CHANNEL_DATA);
                Message msg = mSubHandler.obtainMessage();
                msg.what = SUB_MSG_LOAD_CHANNEL_DATA;
                msg.obj = mGroupListAdapter.getItem(0);
                mSubHandler.sendMessageDelayed(msg, 30);
            }

            mEpgMenuUIHandler.removeMessages(UI_MSG_REFRESH_TIME_LINE);
            mEpgMenuUIHandler.sendEmptyMessageDelayed(UI_MSG_REFRESH_TIME_LINE, 60 * 1000);
        }
    }

    private ProgramsDataManager.QueryFinishListener queryFinishListener = new ProgramsDataManager.QueryFinishListener() {
        @Override
        public void queryFinishCallback() {
            Log.d(TAG, "queryFinishCallback call");

            /**
             * 这里必须重新定位,找到哪个program是被选中状态,不然从Genres回到ListView时,EPG会出现无选中的program。
             */
            mEpgView.findBeSelectedProgram();

            mEpgMenuUIHandler.sendEmptyMessage(UI_MSG_REDRAW_EGPVIEW);
        }
    };

    public Handler mEpgMenuUIHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值