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(@NonNull Message msg) {
            switch (msg.what) {
                case UI_MSG_UPDATE_GROUP_CHANNEL_DATA:
                    Log.d("wujiang", "receive UI_MSG_UPDATE_GROUP_CHANNEL_DATA");
                    mChangedGroup = true;

                    setCurrentChannelId(mChannelDataManager.getLastWatchChannelId());
                    mChannelListAdapter.setChannelsData(mCurrentGroupChannels);
                    mEpgView.mDefaultFocusPosition = getEpgViewDefaultFocusPosition(mCurrentChannelId);

                    mEpgView.setChannelsData(getContext(), mCurrentGroupChannels, EpgMenuFragment.this, mEpgView.mDefaultFocusPosition);
                    mEpgView.scrollTo(mEpgView.getScrollX(), 0);
                    mEpgView.mSkipDraw = true;
                    mChannelListView.setSelection(mEpgView.mDefaultFocusPosition);

                    if (mChannelListView.isFocused()) {
                        mEpgView.recalculateAndRedraw(false, mEpgView.mDefaultFocusPosition, true);
                    } else {
                        mEpgView.recalculateAndRedraw(false, mEpgView.mDefaultFocusPosition, false);
                    }

                    mSubHandler.removeMessages(SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA);

                    //去数据里查询当前页节目的EPG数据
                    Message obtainMessage = mSubHandler.obtainMessage();
                    obtainMessage.what = SUB_MSG_LOAD_CHANNELS_PROGRAM_DATA;
                    obtainMessage.arg1 = mEpgView.mDefaultFocusPosition;
                    obtainMessage.arg2 = 0;
                    mSubHandler.sendMessage(obtainMessage);
                    break;

                case UI_MSG_GROUP_LIST_GET_FOCUS:
                    Log.d(TAG, "UI_MSG_GROUP_LIST_GET_FOCUS");
                    mEpgView.setDrawNeedSelect(false);
                    mEpgView.redraw();

                    mGroupListView.requestFocus();
                    mGroupListView.setSelection(mLastGroupPosition);
                    break;

                case UI_MSG_REFRESH_TIME_LINE:
                    mEpgView.redraw();
                    sendEmptyMessageDelayed(UI_MSG_REFRESH_TIME_LINE, 60 * 1000);
                    break;

                case UI_MSG_REDRAW_EGPVIEW:
                    Log.d(TAG, "UI_MSG_REDRAW_EGPVIEW");
                    mEpgView.redraw();
                    break;

                case UI_MSG_SHOW_NEXT_DAY_EPG:
                    mEpgView.moveToNextDay();
                    break;

                case UI_MSG_SHOW_PREV_DAY_EPG:
                    mEpgView.moveToPrevDay();
                    break;

                default:
                    break;
            }
        }
    };

     EpgGroupListAdapter相关代码:

public class EpgGroupListAdapter extends ArrayAdapter<EpgChannelGroup> {
    private static final String TAG = EpgGroupListAdapter.class.getSimpleName();
    private LayoutInflater mInflater;
    private long mCurrentGroupPosition;
    private ArrayList<EpgChannelGroup> mGroupList;
    private EpgChannelGroup mCurrentGroupInfo;

    public EpgGroupListAdapter(Context context, ArrayList<EpgChannelGroup> groupList) {
        super(context, 0);
        mInflater = LayoutInflater.from(context);

        mGroupList = new ArrayList<EpgChannelGroup>();
        mGroupList.addAll(groupList);
    }

    public void setData(ArrayList<EpgChannelGroup> list) {
        mGroupList.clear();
        mGroupList.addAll(list);

        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mGroupList.size();
    }

    @Nullable
    @Override
    public EpgChannelGroup getItem(int position) {
        return mGroupList.get(position);
    }

    @Override
    public int getPosition(@Nullable EpgChannelGroup item) {
        return super.getPosition(item);
    }

    public void setCurrentGroupPosition(long position) {
        mCurrentGroupPosition = position;
    }

    public void setCurrentGroupInfo(EpgChannelGroup epgChannelGroup) {
        mCurrentGroupInfo = epgChannelGroup;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        TextView textView;
        if (null == convertView) {
            convertView = mInflater.inflate(R.layout.epg_group_list, parent, false);
            textView = (TextView) convertView.findViewById(R.id.txt_title);
            convertView.setTag(textView);
        } else {
            textView = (TextView) convertView.getTag();
        }
        textView.setText(mGroupList.get(position).getName());

        if (position < mGroupList.size() && mCurrentGroupInfo.equals(mGroupList.get(position))) {
            convertView.setBackgroundResource(R.drawable.sel_epg_group_list_selected);
        } else {
            convertView.setBackgroundResource(R.drawable.sel_epg_group_list_normal);
        }

        return convertView;
    }
}

     ChannelListAdapter相关代码:

public class ChannelListAdapter extends BaseAdapter {
    private final String TAG = ChannelListAdapter.class.getSimpleName();
    private List<Channel> channelList;
    private LayoutInflater mInflater;
    private long mCurrentChannelId;

    public ChannelListAdapter(Context context, ArrayList<Channel> channelArrayList) {
        super();
        mInflater = LayoutInflater.from(context);

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

        channelList.clear();
        channelList.addAll(channelArrayList);
    }

    public void setCurrentChannelId(long id) {
        mCurrentChannelId = id;
    }

    public void setChannelsData(ArrayList<Channel> channelArrayList) {
        if (channelList == null) {
            channelList = new ArrayList<>();
        }

        channelList.clear();
        channelList.addAll(channelArrayList);
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return channelList.size();
    }

    @Override
    public Channel getItem(int i) {
        if (getCount() > 0) {
            return channelList.get(i);
        }
        return null;
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ChannelListViewHolder viewHolder;

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.epg_channel_list_item, parent, false);

            viewHolder = new ChannelListViewHolder();
            viewHolder.channelIndex = (TextView) convertView.findViewById(R.id.channel_index);
            viewHolder.channelName = (TextView) convertView.findViewById(R.id.channel_name);
            viewHolder.channelFav = (ImageView) convertView.findViewById(R.id.channel_fav);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ChannelListViewHolder) convertView.getTag();
        }

        if (position < channelList.size()) {
            Channel channel = channelList.get(position);
            if (CommonUtils.isOnLcnSwitch()) {
                viewHolder.channelIndex.setText(channel.getDisplayNumber());
            } else {
                viewHolder.channelIndex.setText(String.valueOf(position + 1));
            }
            viewHolder.channelName.setText(channel.getDisplayName());
            viewHolder.channelFav.setVisibility(View.GONE);
        }

        if (position < channelList.size() && mCurrentChannelId == channelList.get(position).getId()) {
            convertView.setBackgroundResource(R.drawable.sel_epg_channel_list_selected);
        } else {
            convertView.setBackgroundResource(R.drawable.sel_epg_channel_list_normal);
        }

        return convertView;
    }

    public class ChannelListViewHolder {
        TextView channelIndex;
        TextView channelName;
        ImageView channelFav;
    }
}

    ProgramsDataManager相关代码:

public class ProgramsDataManager {
    public static String TAG = ProgramsDataManager.class.getSimpleName();
    //public static String TAG = "wujiang";
    private static ProgramsDataManager instance = null;
    private HashMap<Long, ArrayList<Program>> mChannelsProgramMap;
    private QueryChannelsProgramsManager mQueryChannelsProgramsManager;
    private List<QueryFinishListener> mListenerList;
    private boolean mIsQuerying = false;

    public static ProgramsDataManager getInstance() {
        if (null == instance) {
            instance = new ProgramsDataManager();
        }
        return instance;
    }

    public ProgramsDataManager() {
        mChannelsProgramMap = new HashMap<>();
        mListenerList = new ArrayList<>();
        mQueryChannelsProgramsManager = ((QueryChannelsProgramsManager) DataFactory.get().getManager(TvApplication.getAppContext(), DataFactory.QUERY_PROGRAM_MANAGER));

        mQueryChannelsProgramsManager.addListener(new ProgramLoadedListener() {
            @Override
            public void onProgramLoadedCallback(Map<Long, ? extends ArrayList<Program>> channelsProgramMaps) {
                Log.d(TAG, "onProgramLoadedCallback: channelsProgramMaps size = " + channelsProgramMaps.size());

                if (channelsProgramMaps.size() <= 0) {
                    Log.d(TAG, "onProgramLoadedCallback: channelsProgramMaps size = 0");
                    mIsQuerying = false;
                    return;
                }

                HashMap<Long, ArrayList<Program>> programMaps = (HashMap<Long, ArrayList<Program>>) channelsProgramMaps;
                Set<Map.Entry<Long, ArrayList<Program>>> setEntry = programMaps.entrySet();
                Iterator<Map.Entry<Long, ArrayList<Program>>> iterator = setEntry.iterator();

                while (iterator.hasNext()) {
                    Map.Entry<Long, ArrayList<Program>> entry = iterator.next();
                    Long channel_id = entry.getKey();
                    ArrayList<Program> programArrayList = entry.getValue();

                    //Log.d(TAG, "onProgramLoadedCallback: channel_id = " + channel_id);
                    //Log.d(TAG, "onProgramLoadedCallback: event size = " + programArrayList.size());

                    //for (int i = 0; i < programArrayList.size(); i++) {
                    //    Program program = programArrayList.get(i);
                    //    Log.d(TAG, "onProgramLoadedCallback: event name = " + program.getTitle());
                    //}

                    mChannelsProgramMap.remove(channel_id);
                    mChannelsProgramMap.put(channel_id, programArrayList);
                }

                for (QueryFinishListener listener : mListenerList) {
                    listener.queryFinishCallback();
                }

                mIsQuerying = false;
            }
        });
    }

    /**
     * 查询一些节目的EPG信息
     * @param startTime     起始时间
     * @param endTime       结束时间
     * @param channelsId    channel集合
     * @param needClearData 是否清除掉channel的EPG
     */
    public void queryChannelsProgramFromDatabase(Long startTime, Long endTime, ArrayList<Long> channelsId, int needClearData) {
        ArrayList<Long> needQueryChannels = new ArrayList<>();

        mIsQuerying = true;

        long queryEarlyMorningTime1 = EPGUtils.getDayStartTime(startTime);
        long queryEarlyMorningTime2 = EPGUtils.getDayStartTime(endTime);
        long queryEarlyMorningTime3 = queryEarlyMorningTime2 + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;
        int queryDays;

        if (((endTime / EPGUtils.ONE_THOUSAND  - queryEarlyMorningTime1 / EPGUtils.ONE_THOUSAND) % EPGUtils.DAY_OF_SECONDS) == 0) {
            queryDays = (int) ((endTime / EPGUtils.ONE_THOUSAND - queryEarlyMorningTime1 / EPGUtils.ONE_THOUSAND) / EPGUtils.DAY_OF_SECONDS);
        } else {
            queryDays = (int) ((queryEarlyMorningTime3 / EPGUtils.ONE_THOUSAND - queryEarlyMorningTime1 / EPGUtils.ONE_THOUSAND) / EPGUtils.DAY_OF_SECONDS);
        }

        //Log.d(TAG, "queryChannelsProgramFromDatabase: queryEarlyMorningTime1 = " + queryEarlyMorningTime1);
        //Log.d(TAG, "queryChannelsProgramFromDatabase: queryEarlyMorningTime2 = " + queryEarlyMorningTime2);
        //Log.d(TAG, "queryChannelsProgramFromDatabase: queryEarlyMorningTime3 = " + queryEarlyMorningTime3);
        //Log.d(TAG, "queryChannelsProgramFromDatabase: queryDays = " + queryDays);

        long lastDayEndTime = queryEarlyMorningTime1 + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND * queryDays;
        long lastDayStartTime = lastDayEndTime - EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;

        //Log.d(TAG, "queryChannelsProgramFromDatabase: lastDayEndTime = " + lastDayEndTime);
        //Log.d(TAG, "queryChannelsProgramFromDatabase: lastDayStartTime = " + lastDayStartTime);

        for (Long channel_id : channelsId) {
            boolean haveEpgDataInCache = false;

            if (needClearData == 1) {
                mChannelsProgramMap.remove(channel_id);
            } else {
                ArrayList<Program> programArrayList = mChannelsProgramMap.get(channel_id);

                if (programArrayList == null || programArrayList.size() <= 0) {
                    needQueryChannels.add(channel_id);
                    continue;
                }

                //Log.d(TAG, "queryChannelsProgramFromDatabase: programArrayList size =  " + programArrayList.size());

                for (int i = programArrayList.size() - 1; i >= 0; i--) {
                    Program program = programArrayList.get(i);

                    if (program.getStartTimeUtcMillis() > lastDayStartTime && program.getEndTimeUtcMillis() <= lastDayEndTime) {
                        Log.d(TAG, "queryChannelsProgramFromDatabase: There have epg data in cache, its channel id =  " + channel_id);
                        haveEpgDataInCache = true;
                        break;
                    }
                }
            }

            //Log.d(TAG, "queryChannelsProgramFromDatabase: haveEpgDataInCache =  " + haveEpgDataInCache);

            if (!haveEpgDataInCache) {
                needQueryChannels.add(channel_id);
            }
        }

        Log.d(TAG, "queryChannelsProgramFromDatabase: needQueryChannels size = " + needQueryChannels.size());
        mQueryChannelsProgramsManager.loadChannelsProgramsFromDB(startTime, endTime, needQueryChannels);
    }

    /**
     * 从Map中删除某个节目的EPG信息列表
     * @param channel_id
     */
    public void removeProgramDataByChannelId(long channel_id) {
        mChannelsProgramMap.remove(channel_id);
    }

    /**
     * 增加某个节目的EPG列表到Map中
     * @param channel_id
     * @param list
     */
    public void addProgramDataByChannelId(long channel_id, ArrayList<Program> list) {
        mChannelsProgramMap.remove(channel_id);
        mChannelsProgramMap.put(channel_id, list);
    }

    /**
     * 获取某个节目的EPG信息列表
     * @param channel_id
     * @return
     */
    public ArrayList<Program> getChannelProgramsByChannelId(long channel_id) {
        return mChannelsProgramMap.get(channel_id);
    }

    /**
     * 获取某个节目的EPG信息列表中指定位置的Program
     * @param channel_id
     * @param position
     * @return
     */
    public Program getProgramOfChannelByPosition(long channel_id, int position) {
        Program program = null;

        try {
            program = mChannelsProgramMap.get(channel_id).get(position);
        } catch (Exception ex) {
            Log.e(TAG, "getProgramOfChannelByPosition = " + channel_id + ", position = " + position);
        }

        return program;
    }

    /**
     * 获取program在它所属的channel的program list中的位置
     * @param channel_id
     * @param program
     * @return
     */
    public int getPositionOfProgram(long channel_id, Program program) {
        if (program == null) {
            Log.e(TAG, "getPositionOfProgram: program == null");
            return -1;
        }

        if (mChannelsProgramMap.get(channel_id) == null || mChannelsProgramMap.get(channel_id).size() < 1) {
            Log.d(TAG, "getPositionOfProgram: channel_id: " + channel_id + ", its program list is empty.");
            return -1;
        }

        //Log.d("wujiang", "getPositionOfProgram 3");
        //Log.d("wujiang", "getPositionOfProgram: program start time = " + program.getStartTimeUtcMillis());
        //Log.d("wujiang", "getPositionOfProgram: program end time = " + program.getEndTimeUtcMillis());
        return mChannelsProgramMap.get(channel_id).indexOf(program);
    }

    /**
     * 获取到指定节目program的数量
     * @return
     */
    public int getProgramCountOfChannel(long channel_id) {
        if (mChannelsProgramMap.get(channel_id) == null) {
            return 0;
        }

        return mChannelsProgramMap.get(channel_id).size();
    }

    /**
     * 清楚掉所有program的选中状态
     */
    public void clearSelection() {
        if (mChannelsProgramMap.size() > 0) {
            Set<Map.Entry<Long, ArrayList<Program>>> setEntry = mChannelsProgramMap.entrySet();
            Iterator<Map.Entry<Long, ArrayList<Program>>> iterator = setEntry.iterator();

            while (iterator.hasNext()) {
                Map.Entry<Long, ArrayList<Program>> entry = iterator.next();
                ArrayList<Program> programArrayList = entry.getValue();

                for (Program program : programArrayList) {
                    program.setSelected(false);
                }
            }
        }
    }

    /**
     * 清除Map中的数据
     */
    public void clearData() {
        mChannelsProgramMap.clear();
    }

    public boolean isQuerying() {
        return mIsQuerying;
    }

    public void addListener(QueryFinishListener listener) {
        mListenerList.add(listener);
    }

    public void removeListener(QueryFinishListener listener) {
        mListenerList.remove(listener);
    }

    public interface QueryFinishListener {
        void queryFinishCallback();
    }
}

    QueryChannelsProgramsManager相关代码:

import static com.nes.datalibrary.manager.ProgramDataManager.PARAM_END_TIME;
import static com.nes.datalibrary.manager.ProgramDataManager.PARAM_START_TIME;

public class QueryChannelsProgramsManager {
    private final String TAG = QueryChannelsProgramsManager.class.getSimpleName();
    private Context mContext;
    private ExecutorService DB_EXECUTOR = Executors.newSingleThreadExecutor(new NamedThreadFactory("query-db"));

    //存放当前查询节目的epg信息
    private HashMap<Long, ArrayList<Program>> mAllChannelProgramMap = new HashMap<>();
    private ArrayList<ProgramLoadedListener> mListenerList;

    private TvClock mTvClock;
    private long execute_time = 0;

    private final String SORT_BY_TIME = (TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
            + ", "
            + TvContract.Programs.COLUMN_CHANNEL_ID
            + ", "
            + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS);

    public QueryChannelsProgramsManager(Context context) {
        mContext = context;
        mListenerList = new ArrayList<>();
        mTvClock = new TvClock(context);
    }

    public void loadChannelsProgramsFromDB(Long startTime, Long endTime, ArrayList<Long> channelsId) {
        QueryParams queryParams = new QueryParams(startTime, endTime, channelsId);
        LoadProgramFromDBTask loadProgramFromDBTask = new LoadProgramFromDBTask(DB_EXECUTOR);
        loadProgramFromDBTask.executeOnDbThread(queryParams);
    }

    public class LoadProgramFromDBTask extends AsyncDbTask<QueryParams, Void, Map<Long, ArrayList<Program>>> {
        protected LoadProgramFromDBTask(Executor mExecutor) {
            super(mExecutor);
        }

        @Override
        protected Map<Long, ArrayList<Program>> doInBackground(QueryParams... params) {
            execute_time = mTvClock.currentTimeMillis();

            Long startTime = params[0].getStartTime();
            Long endTime = params[0].getEndTime();
            ArrayList<Long> channelIdList = params[0].getQueryChannels();

            Log.d("wujiang", "doInBackground: startTime = " + startTime);
            Log.d("wujiang", "doInBackground: endTime = " + endTime);

            mAllChannelProgramMap.clear();

            for (Long channelId : channelIdList) {
                ArrayList<Program> programArrayList = queryOneChannelPrograms(channelId, startTime, endTime);
                mAllChannelProgramMap.put(channelId.longValue(), programArrayList);
            }

            Log.d(TAG, "doInBackground: spend time  = " + (mTvClock.currentTimeMillis() - execute_time));
            return mAllChannelProgramMap;
        }

        @Override
        protected void onPostExecute(Map<Long, ArrayList<Program>> longArrayListMap) {
            for (ProgramLoadedListener listener : mListenerList) {
                listener.onProgramLoadedCallback(longArrayListMap);
                mAllChannelProgramMap.clear();
            }
        }
    }

    private Map<Long, ArrayList<Program>> queryMultiChannelPrograms(ArrayList<Long> channelIdList, Long start_time, Long end_time) {
        HashMap<Long, ArrayList<Program>> programMaps = new HashMap<>();
        programMaps.clear();

        //存放了所有节目的EPG信息
        ArrayList<Program> allChannelProgramsList = new ArrayList<>();
        allChannelProgramsList.clear();

        Cursor cursor = null;

        Uri uri = TvContract.Programs.CONTENT_URI.buildUpon()
                .appendQueryParameter(PARAM_START_TIME, start_time.toString())
                .appendQueryParameter(PARAM_END_TIME, end_time.toString())
                .build();
        try {
            cursor = mContext.getContentResolver().query(uri, Program.PROJECTION, null, null, SORT_BY_TIME);

            if (cursor == null) {
                return null;
            }

            int programsCount = cursor.getCount();
            if (programsCount > 0) {
                cursor.moveToFirst();

                while (!cursor.isAfterLast()) {
                    allChannelProgramsList.add(Program.fromCursor(cursor));
                    cursor.moveToNext();
                }
            } else {
                return null;
            }
        } finally {
            try {
                if (null != cursor && !cursor.isClosed()) {
                    cursor.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //Log.d("wujiang", "queryMultiChannelPrograms: allChannelProgramsList size = " + allChannelProgramsList.size());

        Program lastReadProgram = null;
        for (Program program : allChannelProgramsList) {
            if (Program.isDuplicate(program, lastReadProgram)) {
                Log.d(TAG, "queryMultiChannelPrograms: duplicate program ...");
                continue;
            } else {
                lastReadProgram = program;
            }

            ArrayList<Program> oneChannelProgramArrayList = programMaps.get(program.getChannelId());
            if (oneChannelProgramArrayList == null) {
                oneChannelProgramArrayList = new ArrayList<>();
                programMaps.put(program.getChannelId(), oneChannelProgramArrayList);
            }
            oneChannelProgramArrayList.add(program);
        }

        return programMaps;
    }

    /**
     * 查询某个节目指定时间内的EPG信息
     *
     * @param channel_id
     * @param start_time
     * @param end_time
     * @return event列表
     */
    private ArrayList<Program> queryOneChannelPrograms(Long channel_id, Long start_time, Long end_time) {
        Log.d(TAG, "queryOneChannelPrograms: start query channel_id = " + channel_id + " epg from database");
        Uri uri = TvContract.Programs.CONTENT_URI.buildUpon()
                .appendQueryParameter(PARAM_START_TIME, start_time.toString())
                .appendQueryParameter(PARAM_END_TIME, end_time.toString())
                .build();

        //存放该节目EPG数据,去掉了重复的数据
        ArrayList<Program> programsList = new ArrayList<>();
        programsList.clear();

        Program lastReadProgram = null;
        Cursor cursor = null;

        try {
            cursor = mContext.getContentResolver().query(uri, Program.PROJECTION, TvContract.Programs.COLUMN_CHANNEL_ID + "=?", new String[]{channel_id.toString()}, TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS);
            if (cursor == null) {
                return programsList;
            }

            int programsCount = cursor.getCount();
            if (programsCount > 0) {
                cursor.moveToFirst();

                while (!cursor.isAfterLast()) {
                    Program program = Program.fromCursor(cursor);

                    if (Program.isDuplicate(program, lastReadProgram)) {
                        Log.d(TAG, "queryOneChannelPrograms: duplicate program ...");
                        cursor.moveToNext();
                        continue;
                    } else {
                        if (lastReadProgram != null && (lastReadProgram.getEndTimeUtcMillis() != program.getStartTimeUtcMillis())) {
                            /**
                             * 两个EPG时间不是连续的,需要补上空白EPG,不然UI会出现异常
                             */
                            long fill_time = program.getStartTimeUtcMillis() - lastReadProgram.getEndTimeUtcMillis();

                            if (fill_time > 0) {
                                Program fill_program = new Program();
                                fill_program.setStartTimeUtcMillis(lastReadProgram.getEndTimeUtcMillis());
                                fill_program.setEndTimeUtcMillis(program.getStartTimeUtcMillis());
                                fill_program.setId(lastReadProgram.getId());
                                fill_program.setTitle("No Information");

                                programsList.add(fill_program);
                            }
                        }

                        programsList.add(program);
                        lastReadProgram = program;
                    }
                    cursor.moveToNext();
                }
            }
        } finally {
            try {
                if (null != cursor && !cursor.isClosed()) {
                    cursor.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (programsList.size() > 0) {
            /** 如果第一个event的开始时间就比当前时间大,则在它前面补上一个event **/
            Program firstProgram = programsList.get(0);
            long current_time = mTvClock.currentTimeMillis();

            if (firstProgram.getStartTimeUtcMillis() > current_time) {
                Program program = new Program();
                program.setStartTimeUtcMillis(current_time - 30 * 60 * 1000);
                program.setEndTimeUtcMillis(firstProgram.getStartTimeUtcMillis());
                program.setId(firstProgram.getId());
                program.setTitle("No Information");
                programsList.add(0, program);
            }

            /** 如果最后一个event的结束时间没有到达end_time, 则在它后面补上一个event **/
            if (lastReadProgram.getEndTimeUtcMillis() < end_time.longValue()) {
                Program program = new Program();
                program.setStartTimeUtcMillis(lastReadProgram.getEndTimeUtcMillis());
                program.setEndTimeUtcMillis(end_time);
                program.setId(lastReadProgram.getId());
                program.setTitle("No Information");
                programsList.add(program);
            }
        }

        return programsList;
    }

    public void addListener(ProgramLoadedListener listener) {
        mListenerList.add(listener);
    }

    public void removeListener(ProgramLoadedListener listener) {
        mListenerList.remove(listener);
    }

    private class QueryParams {
        private Long startTime;
        private Long endTime;
        private ArrayList<Long> queryChannels;

        public QueryParams(Long startTime, Long endTime, ArrayList<Long> queryChannels) {
            this.startTime = startTime;
            this.endTime = endTime;
            this.queryChannels = queryChannels;
        }

        public Long getStartTime() {
            return startTime;
        }

        public Long getEndTime() {
            return endTime;
        }

        public ArrayList<Long> getQueryChannels() {
            return queryChannels;
        }
    }
}

    这里,AsyncDbTask的代码我就不贴出来了。

    EpgView是一个自定义的类,EPG的数据就是靠它显示出来的,我把全部的代码都贴出来。

public class EpgView extends ViewGroup {
    private final String TAG = EpgView.class.getSimpleName();

    public static int DAYS_BACK_MILLIS;
    public static final int DAYS_FORWARD_MILLIS = 5 * 24 * 60 * 60 * 1000;
    private static int HOURS_IN_VIEWPORT_MILLIS = 2 * 60 * 60 * 1000;
    private static final int TIME_LABEL_SPACING_MILLIS = 30 * 60 * 1000;
    public static final int MaxItemCountInOnePage = 10;

    public static final int UP_DIRECTION = 0;
    public static final int DOWN_DIRECTION = 1;

    private Context contextObj;
    private EpgMenuFragment mEpgMenuFragment = null;

    private int cuDays = 0;
    private boolean isMoveRight;
    private boolean isMoveLeft;
    private boolean isAutoFocusChange;
    private boolean mNeedDrawSelectedProgram = false;   //选中的event是否要变成焦点颜色
    private boolean drawCurrentTimeLine = false;

    private int adjustment_height = 0;
    private boolean adjust_only_one_time = false;

    private int mChannelLayoutHeight;       //一行epg数据所占的屏幕高度
    private int mChannelLayoutPadding;      //epg数据行每个event的间距
    private int mChannelLayoutWidth;        //在原始代码中,这个值是channel listview的宽度,这里我们定义成0
    private final int mChannelLayoutMargin; //epg数据行的间距
    private int mTwoListViewWidth;
    private int mChannelListViewWidth;
    private int mTimeBarTextSize;

    private final Rect mClipRect;
    private final Rect mDrawingRect;
    private final Rect mMeasuringRect;
    private final Paint mPaint;
    private final Scroller mScroller;

    private final int mEPGBackground;           //EPG View的背景颜色
    private final int mEventLayoutBackground;   //event无焦点时的颜色
    private final int mEventLayoutTextColor;    //event文字颜色
    private final int mEventLayoutSelected;     //event选中时的颜色
    private final int mEventLayoutTextSize;     //event title字体大小
    private final int mTimeBarHeight;           //time bar的高度
    private final int mTimerBarBackground;      //time bar的背景颜色
    private final int mTimeBarLineWidth;        //时刻线的宽度
    private final int mTimeBarLineColor;        //时刻线的颜色
    private final int mEventLayoutBackgroundCurrent;
    private final int mHelpBarHeight;

    //event显示区域(2个小时)里,每一个像素占的毫秒数
    private long mMillisPerPixel;

    //时刻表上最左边的那个时间,就是EPG主界面刚显示时当前的时间,单位毫秒
    private long mTimeOffset;

    //当前EPG信息最左侧、最右侧的时间,按左右键他们的值是会变得,单位毫秒
    private long mTimeLowerBoundary;
    private long mTimeUpperBoundary;

    private int mMaxHorizontalScroll;
    private int mMaxVerticalScroll;

    private List<Channel> serviceList;
    public Program currentProgram = null;
    private Program referenceEvent;
    private int referenceEventPosition;

    public int mDefaultFocusPosition = 0;
    public int currentChannelPosition = -1;

    private Bitmap mRecordBitmap;
    private Bitmap mReminderBitmap;
    private Bitmap mRecordingBitmap;

    private TvClock mTvClock;
    public boolean mSkipDraw = false;

    public EpgView(Context context) {
        this(context, null);
    }

    public EpgView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EpgView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //Log.d("wujiang", "EpgView: call this function");

        DAYS_BACK_MILLIS = 0;
        contextObj = context;

        if (cuDays > 0) {
            DAYS_BACK_MILLIS = cuDays * 24 * 60 * 60 * 1000;
        } else {
            DAYS_BACK_MILLIS = 0;
        }

        mTvClock = new TvClock(contextObj);

        setWillNotDraw(false);

        resetBoundaries();

        mDrawingRect = new Rect();
        mClipRect = new Rect();
        mMeasuringRect = new Rect();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTypeface(Typeface.createFromAsset(context.getAssets(), "Roboto-Light.ttf"));

        mScroller = new Scroller(context);
        mScroller.setFriction(ViewConfiguration.getScrollFriction());

        mEPGBackground = getResources().getColor(R.color.epg_background);
        mChannelLayoutMargin = getResources().getDimensionPixelSize(R.dimen.epg_line_space);
        mChannelLayoutPadding = getResources().getDimensionPixelSize(R.dimen.epg_event_space);
        mChannelLayoutHeight = getResources().getDimensionPixelSize(R.dimen.px_85_0);
        mChannelLayoutWidth = getResources().getDimensionPixelSize(R.dimen.zero);
        mTwoListViewWidth = getResources().getDimensionPixelSize(R.dimen.epg_group_listview_width)
                + getResources().getDimensionPixelSize(R.dimen.epg_channel_listview_width)
                + getResources().getDimensionPixelSize(R.dimen.listviews_space);
        mChannelListViewWidth = getResources().getDimensionPixelSize(R.dimen.epg_channel_listview_width);

        mEventLayoutBackground = getResources().getColor(R.color.epg_channel_list_item_normal);
        mEventLayoutTextColor = getResources().getColor(R.color.white);
        mEventLayoutSelected = getResources().getColor(R.color.color_blue);
        mEventLayoutTextSize = getResources().getDimensionPixelSize(R.dimen.epg_event_text_size);
        mTimeBarHeight = getResources().getDimensionPixelSize(R.dimen.px_104_0);
        mTimeBarLineWidth = getResources().getDimensionPixelSize(R.dimen.epg_time_line_width);
        mTimeBarLineColor = getResources().getColor(R.color.epg_time_line);
        mTimerBarBackground = getResources().getColor(R.color.epg_help_bar);
        mEventLayoutBackgroundCurrent = getResources().getColor(R.color.epg_background_black);
        mTimeBarTextSize = getResources().getDimensionPixelSize(R.dimen.epg_time_bar_text_size);
        mHelpBarHeight = getResources().getDimensionPixelSize(R.dimen.epg_help_bar_height);

        initData();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean b, int left, int top, int right, int bottom) {

    }

    private void initData() {
        mRecordBitmap = ((BitmapDrawable) contextObj.getDrawable(R.mipmap.ic_schedule_rec)).getBitmap();
        mReminderBitmap = ((BitmapDrawable) contextObj.getDrawable(R.mipmap.ic_schedule_play)).getBitmap();
        mRecordingBitmap = ((BitmapDrawable) contextObj.getDrawable(R.mipmap.ic_programm_recording)).getBitmap();
    }

    private boolean isEventVisible(final long start, final long end) {
        return (start >= mTimeLowerBoundary && start <= mTimeUpperBoundary)
                || (end > mTimeLowerBoundary && end <= mTimeUpperBoundary)
                || (start <= mTimeLowerBoundary && end >= mTimeUpperBoundary);
    }

    private void setEventDrawingRectangle(final int channelPosition, final long start, final long end, final Rect drawingRect, String eventName) {
        if (start < mTimeOffset) {
            drawingRect.left = 0;//getXFrom(mTimeOffset);
        } else {
            drawingRect.left = getXFrom(start);
        }

        drawingRect.top = getTopFrom(channelPosition);
        drawingRect.right = getXFrom(end) - mChannelLayoutMargin;

        drawingRect.bottom = drawingRect.top + mChannelLayoutHeight;
    }

    public int getFirstVisibleChannelPosition() {
        final int y = getScrollY();
        int position = (y - mChannelLayoutMargin - mTimeBarHeight) / (mChannelLayoutHeight + mChannelLayoutMargin);

        if (position < 0) {
            position = 0;
        }
        return position;
    }

    public int getLastVisibleChannelPosition() {
        final int y = getScrollY();
        final int totalChannelCount = serviceList.size();
        final int screenHeight = getHeight();
        int position = (y + screenHeight + mTimeBarHeight - mChannelLayoutMargin - mHelpBarHeight) / (mChannelLayoutHeight + mChannelLayoutMargin);

        if (position > totalChannelCount - 1) {
            position = totalChannelCount - 1;
        }

        // Add one extra row if we don't fill screen with current..
        return (y + screenHeight) > (position * mChannelLayoutHeight) && position < totalChannelCount - 1 ? position + 1 : position;
    }

    private int getLastBlankLineChannelPosition() {
        final int y = getScrollY();
        final int totalChannelCount = MaxItemCountInOnePage;
        final int screenHeight = getHeight();
        int position = (y + screenHeight + mTimeBarHeight - mChannelLayoutMargin - mHelpBarHeight) / (mChannelLayoutHeight + mChannelLayoutMargin);

        if (position > totalChannelCount - 1) {
            position = totalChannelCount - 1;
        }

        // Add one extra row if we don't fill screen with current..
        return (y + screenHeight) > (position * mChannelLayoutHeight) && position < totalChannelCount - 1 ? position + 1 : position;
    }

    private void drawBlankEvents(Canvas canvas, Rect drawingRect) {
        final int firstPos = getFirstVisibleChannelPosition();
        final int lastPos = getLastBlankLineChannelPosition();

        for (int pos = firstPos; pos <= lastPos; pos++) {
            mClipRect.left = getScrollX() + mChannelLayoutWidth + mChannelLayoutMargin;
            mClipRect.top = getTopFrom(pos);
            mClipRect.right = getScrollX() + getWidth();
            mClipRect.bottom = mClipRect.top + mChannelLayoutHeight;

            canvas.save();
            canvas.clipRect(mClipRect);

            mPaint.setColor(mEventLayoutBackground);
            canvas.drawRect(mClipRect, mPaint);

            canvas.restore();
        }
    }

    private void drawTimeBar(Canvas canvas, Rect drawingRect) {
        //drawingRect.left = getScrollX() + mChannelLayoutWidth + mChannelLayoutMargin;
        //DLog.d("wujiang", "drawTimeBar: call this function");
        drawingRect.left = getScrollX() + mChannelLayoutWidth;
        drawingRect.top = getScrollY();
        drawingRect.right = drawingRect.left + getWidth();
        drawingRect.bottom = drawingRect.top + mTimeBarHeight;

        //DLog.d("wujiang", "drawTimeBar: getScrollX = " + getScrollX());
        //DLog.d("wujiang", "drawTimeBar: drawingRect.left = " + drawingRect.left);

        //mClipRect.left = getScrollX() + mChannelLayoutWidth + mChannelLayoutMargin;
        mClipRect.left = getScrollX() + mChannelLayoutWidth;
        mClipRect.top = getScrollY();
        mClipRect.right = drawingRect.left + getWidth();
        mClipRect.bottom = mClipRect.top + mTimeBarHeight;

        //DLog.d("wujiang", "drawTimeBar: mClipRect.left = " + mClipRect.left);

        canvas.save();
        canvas.clipRect(mClipRect);

        // Background
        mPaint.setColor(mTimerBarBackground);
        canvas.drawRect(drawingRect, mPaint);

        // Time stamps
        mPaint.setColor(mEventLayoutTextColor);
        mPaint.setTextSize(mTimeBarTextSize);

        /*for (int i = 0; i < HOURS_IN_VIEWPORT_MILLIS / TIME_LABEL_SPACING_MILLIS; i++) {
            // Get time and round to nearest half hour
            final long time = TIME_LABEL_SPACING_MILLIS *
                    (((mTimeLowerBoundary + (TIME_LABEL_SPACING_MILLIS * i)) +
                            (TIME_LABEL_SPACING_MILLIS / 2)) / TIME_LABEL_SPACING_MILLIS);

            mPaint.setColor(mEventLayoutTextColor);

            canvas.drawText(EPGUtils.get24hTimer(time), getXFrom(time),
                    drawingRect.top + (((drawingRect.bottom - drawingRect.top) / 2) + (mTimeBarTextSize / 2)), mPaint);
        }*/

        int i = 0;
        long time = TIME_LABEL_SPACING_MILLIS * (((mTimeLowerBoundary + (TIME_LABEL_SPACING_MILLIS * i)) + (TIME_LABEL_SPACING_MILLIS / 2)) / TIME_LABEL_SPACING_MILLIS);
        while(time <= mTimeUpperBoundary) {
            mPaint.setColor(mEventLayoutTextColor);

            canvas.drawText(EPGUtils.get24hTimer(time), getXFrom(time), drawingRect.top + (((drawingRect.bottom - drawingRect.top) / 2) + (mTimeBarTextSize / 2)), mPaint);

            i++;
            time = TIME_LABEL_SPACING_MILLIS * (((mTimeLowerBoundary + (TIME_LABEL_SPACING_MILLIS * i)) + (TIME_LABEL_SPACING_MILLIS / 2)) / TIME_LABEL_SPACING_MILLIS);
        }

        canvas.restore();
        //drawTimebarDayIndicator(canvas, drawingRect);
        drawTime();
        drawTimebarBottomStroke(canvas, drawingRect);
    }

    private void drawTime() {
        if (mEpgMenuFragment != null) {
            mEpgMenuFragment.setEpgDateTextViewString(EPGUtils.getWeekdayName(mTimeLowerBoundary));
        }
    }

    private void drawTimebarBottomStroke(Canvas canvas, Rect drawingRect) {
        drawingRect.left = getScrollX();
        drawingRect.top = getScrollY() + mTimeBarHeight;
        drawingRect.right = drawingRect.left + getWidth();
        drawingRect.bottom = drawingRect.top + mChannelLayoutMargin;
        // Bottom stroke
        mPaint.setColor(mEPGBackground);
        canvas.drawRect(drawingRect, mPaint);
    }

    private void drawTimeLine(Canvas canvas, Rect drawingRect) {
        long now = EPGUtils.getCurrentTimeInMillis();
        //DLog.d("wujiang", "drawTimeLine: call this function");

        if (shouldDrawTimeLine(now)) {
            drawingRect.left = getXFrom(now);
            drawingRect.top = getScrollY() + mTimeBarHeight;
            drawingRect.right = drawingRect.left + mTimeBarLineWidth;
            drawingRect.bottom = drawingRect.top + getHeight() - mTimeBarHeight;
            mPaint.setColor(mTimeBarLineColor);
            canvas.drawRect(drawingRect, mPaint);

            //画倒三角形
            Path path2 = new Path();
            path2.moveTo(drawingRect.left + 0.5f, drawingRect.top);     //移动画笔
            path2.lineTo(drawingRect.left - 6, drawingRect.top - 8); //画线
            path2.lineTo(drawingRect.left + 7, drawingRect.top - 8); //画线
            path2.close();
            canvas.drawPath(path2, mPaint);
        }
    }

    private boolean shouldDrawTimeLine(long now) {
        return now >= mTimeLowerBoundary && now < mTimeUpperBoundary;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (serviceList != null && serviceList.size() > 0) {
            //Log.d("wujiang", "onDraw: 111111111111");
            mTimeLowerBoundary = getTimeFrom(getScrollX());
            mTimeUpperBoundary = getTimeFrom(getScrollX() + getWidth());

            //Log.d("wujiang", "onDraw: getScrollX = " + getScrollX());
            //Log.d("wujiang", "onDraw: getScrollY = " + getScrollY());
            //Log.d("wujiang", "onDraw: getWidth = " + getWidth());
            //Log.d("wujiang", "onDraw: mTimeLowerBoundary = " + mTimeLowerBoundary);
            //Log.d("wujiang", "onDraw: mTimeUpperBoundary = " + mTimeUpperBoundary);

            Rect drawingRect = mDrawingRect;
            drawingRect.left = getScrollX();
            drawingRect.top = getScrollY();
            drawingRect.right = drawingRect.left + getWidth();
            drawingRect.bottom = drawingRect.top + getHeight();

            //drawChannelListItems(canvas, drawingRect);
            drawEvents(canvas, drawingRect);
            drawTimeBar(canvas, drawingRect);
            drawTimeLine(canvas, drawingRect);

            //drawResetButton(canvas, drawingRect);

            // If scroller is scrolling/animating do scroll. This applies when doing a fling.
            if (mScroller.computeScrollOffset()) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            }
        } else {
            //Log.d("wujiang", "onDraw: 22222222222");
            mTimeLowerBoundary = getTimeFrom(getScrollX());
            mTimeUpperBoundary = getTimeFrom(getScrollX() + getWidth());

            Rect drawingRect = mDrawingRect;
            drawingRect.left = getScrollX();
            drawingRect.top = getScrollY();
            drawingRect.right = drawingRect.left + getWidth();
            drawingRect.bottom = drawingRect.top + getHeight();

            drawBlankEvents(canvas, drawingRect);
            drawTimeBar(canvas, drawingRect);
        }
    }

    private void drawEvents(Canvas canvas, Rect drawingRect) {
        final int firstPos = getFirstVisibleChannelPosition();
        final int lastPos = getLastVisibleChannelPosition();

        //解决切换组的时候,有多余的刷新EPG内容的操作,而且都是从firstPos = 0开始的。
        if (firstPos == 0 && mSkipDraw) {
            Log.d(TAG, "drawEvents: skip this draw");
            mSkipDraw = false;
            return;
        }

        //Log.d("wujiang", "drawEvents: firstPos = " + firstPos);
        //Log.d("wujiang", "drawEvents: lastPos = " + lastPos);

        for (int pos = firstPos; pos <= lastPos; pos++) {
            // Set clip rectangle
            mClipRect.left = getScrollX() + mChannelLayoutWidth + mChannelLayoutMargin;
            mClipRect.top = getTopFrom(pos);
            mClipRect.right = getScrollX() + getWidth();
            mClipRect.bottom = mClipRect.top + mChannelLayoutHeight;

            canvas.save();
            canvas.clipRect(mClipRect);

            // Draw each event
            boolean foundFirst = false;

            Channel channel = serviceList.get(pos);
            //Log.d("wujiang", "drawEvents: channel name = " + channel.getDisplayName());

            List<Program> epgEvents = ProgramsDataManager.getInstance().getChannelProgramsByChannelId(channel.getId());
            //Log.d("wujiang", "drawEvents: epgEvents size = " + epgEvents.size());

            //记录第一个被画的event
            Program firstDrewEvent = null;

            //记录最后一个被画的event
            Program lastDrewEvent = null;

            if (epgEvents != null && epgEvents.size() > 0) {
                //Log.d("wujiang", "drawEvents: epgEvents.size() = " + epgEvents.size());
                for (Program event : epgEvents) {
                    if (isEventVisible(event.getStartTimeUtcMillis(), event.getEndTimeUtcMillis())) {
                        //Log.d("wujiang", "drawEvents: 111111");
                        lastDrewEvent = event;
                        drawEvent(canvas, pos, event, drawingRect);
                        foundFirst = true;

                        if (firstDrewEvent == null) {
                            firstDrewEvent = event;
                        }
                    } else if (foundFirst) {
                        /** 找到最后一个可见的event后就不需要再往后面去找了,赞! **/
                        break;
                    }
                }

                if (foundFirst == false) {
                    //Log.d("wujiang", "drawEvents: 222222222");
                    drawNoInforEvent(canvas, pos, drawingRect);
                } else if (foundFirst && (firstDrewEvent != null && firstDrewEvent.getStartTimeUtcMillis() > mTimeLowerBoundary)) {
                    //如果第一个被画的前面有空白区,则画一个event进行补全。
                    Log.d("wujiang", "drawEvents: need to draw blank area ahead firstDrewEvent");
                    drawNoInforEventAheadFirstEvent(canvas, pos, drawingRect, firstDrewEvent);
                }

                if (lastDrewEvent != null && lastDrewEvent.getEndTimeUtcMillis() < mTimeUpperBoundary) {
                    //如果最后一个被画的event还没有填满整行,则画一个event进行补全
                    Log.d("wujiang", "drawEvents: need to draw blank area following  lastDrewEvent");
                    drawNoInforEventFollowingLastEvent(canvas, pos, drawingRect, lastDrewEvent);
                }
            } else {
                //Log.d("wujiang", "drawEvents: 3333333333333");
                drawNoInforEvent(canvas, pos, drawingRect);
            }

            canvas.restore();
        }

        //不够MaxItemCountInOnePage行,为了美化UI,补齐剩下的行。 这套UI需要这样
        /*if ((lastPos - firstPos) < MaxItemCountInOnePage) {
            for (int pos = lastPos + 1; pos < MaxItemCountInOnePage; pos++) {
                mClipRect.left = getScrollX() + mChannelLayoutWidth + mChannelLayoutMargin;
                mClipRect.top = getTopFrom(pos);
                mClipRect.right = getScrollX() + getWidth();
                mClipRect.bottom = mClipRect.top + mChannelLayoutHeight;

                canvas.save();
                canvas.clipRect(mClipRect);

                mPaint.setColor(mEventLayoutBackground);
                canvas.drawRect(mClipRect, mPaint);

                canvas.restore();
            }
        }*/
    }

    /**
     * @param canvas
     * @param channelPosition channel的位置
     * @param event           要画的event
     * @param drawingRect     要画的event的位置信息,是通过event的开始、结束时间计算出来的
     */
    private void drawEvent(final Canvas canvas, final int channelPosition, final Program event, final Rect drawingRect) {
        //Log.d("wujiang", "drawEvent: call this function, mNeedDrawSelectedProgram = " + mNeedDrawSelectedProgram);
        setEventDrawingRectangle(channelPosition, event.getStartTimeUtcMillis(), event.getEndTimeUtcMillis(), drawingRect, event.getTitle());

        // Background
        mPaint.setColor(isCurrent(event) && drawCurrentTimeLine ? mEventLayoutBackgroundCurrent : mEventLayoutBackground);

        if (event.isSelected() && mNeedDrawSelectedProgram) {
            //Log.d("wujiang", "drawEvent: set focus color");
            mPaint.setColor(mEventLayoutSelected);
            //Log.d("wujiang", "drawEvent: event.title() = " + event.getTitle());
            Log.d("wujiang", "drawEvent: focus event start_time = " + event.getStartTimeUtcMillis());
            Log.d("wujiang", "drawEvent: focus event end_time = " + event.getEndTimeUtcMillis());
        }

        canvas.drawRect(drawingRect, mPaint);

        long program_channel_id = event.getChannelId();
        long program_start_time = event.getStartTimeUtcMillis();

        if (mEpgMenuFragment != null && mEpgMenuFragment.mScheduleData.size() <= 0) {
            event.setTimerStatus(0);
        } else {
            for (int i = 0; i < mEpgMenuFragment.mScheduleData.size(); i++) {
                ScheduleRecording scheduleRecording = mEpgMenuFragment.mScheduleData.get(i);
                long start_time = scheduleRecording.getStartTime();
                long channel_id = scheduleRecording.getChannelId();

                if (channel_id == program_channel_id && start_time == program_start_time) {
                    //Log.d("wujiang", "drawEvent: 1111111111111111");
                    if (scheduleRecording.getType() == SchedulerContract.Schedules.TYPE_WATCH) {
                        event.setTimerStatus(1);
                        if (drawingRect.right >= getXFrom(mTimeUpperBoundary)) {
                            canvas.drawBitmap(mReminderBitmap, getXFrom(mTimeUpperBoundary) - mReminderBitmap.getWidth() - 4, drawingRect.top + (drawingRect.bottom - drawingRect.top) / 2 - mReminderBitmap.getHeight() / 2, null);
                        } else {
                            canvas.drawBitmap(mReminderBitmap, drawingRect.right - mReminderBitmap.getWidth() - 4, drawingRect.top + (drawingRect.bottom - drawingRect.top) / 2 - mReminderBitmap.getHeight() / 2, null);
                        }
                        break;
                    } else if (scheduleRecording.getType() == SchedulerContract.Schedules.TYPE_RECORD) {
                        //Log.d("wujiang", "drawEvent: 22222222222");
                        event.setTimerStatus(2);
                        if (drawingRect.right >= getXFrom(mTimeUpperBoundary)) {
                            canvas.drawBitmap(mRecordBitmap, getXFrom(mTimeUpperBoundary) - mRecordBitmap.getWidth() - 4, drawingRect.top + (drawingRect.bottom - drawingRect.top) / 2 - mRecordBitmap.getHeight() / 2, null);
                        } else {
                            canvas.drawBitmap(mRecordBitmap, drawingRect.right - mRecordBitmap.getWidth() - 4, drawingRect.top + (drawingRect.bottom - drawingRect.top) / 2 - mRecordBitmap.getHeight() / 2, null);
                        }
                        break;
                    }
                } else {
                    //Log.d("wujiang", "drawEvent: 333333333333333");
                    event.setTimerStatus(0);
                }
            }
        }

        // Add left and right inner padding
        drawingRect.left += mChannelLayoutPadding;
        drawingRect.right -= mChannelLayoutPadding;

        // Text
        mPaint.setColor(mEventLayoutTextColor);
        mPaint.setTextSize(mEventLayoutTextSize);

        //Log.e("TAG", " @ @ @ @ " + event.getGenres());
        // Set Genre
        Rect genreBackgroundRect = null;

        if (genreBackgroundRect == null) {
            genreBackgroundRect = new Rect();
            genreBackgroundRect.set(drawingRect.left, drawingRect.top + mTimeBarHeight + 10, (int) (drawingRect.left), drawingRect.bottom - 5);
        }

        // Move drawing.top so text will be centered (text is drawn bottom>up)
        mPaint.getTextBounds(event.getTitle(), 0, event.getTitle().length(), mMeasuringRect);
        drawingRect.top += (((drawingRect.bottom - drawingRect.top) / 2) + (mMeasuringRect.height() / 2));

        // Text
        /*if (event.isSelected() && mNeedDrawSelectedProgram) {
            mPaint.setColor(Color.YELLOW);
            mPaint.setTextSize(mEventLayoutTextSize);
        } else {
            mPaint.setColor(mEventLayoutTextColor);
            mPaint.setTextSize(mEventLayoutTextSize);
        }*/

        //NEW CHANGES USING SHOW_TYPE of EVENT
        String title = null;

        title = event.getTitle();

        title = title.substring(0, mPaint.breakText(title, true, drawingRect.right - drawingRect.left, null));

        TextPaint textPaint = new TextPaint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(mPaint.getColor());
        textPaint.setTextSize(mPaint.getTextSize());
        textPaint.setStyle(mPaint.getStyle());
        textPaint.setTypeface(mPaint.getTypeface());

        CharSequence txt = null;

        if (event.getStartTimeUtcMillis() < mTimeLowerBoundary) {
            txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - getXFrom(mTimeLowerBoundary)) - mReminderBitmap.getWidth(), TextUtils.TruncateAt.END);
            canvas.drawText(txt, 0, txt.length(), getXFrom(mTimeLowerBoundary) + 6, drawingRect.top, textPaint);
        } else {
            if (drawingRect.right >= getXFrom(mTimeUpperBoundary)) {
                txt = TextUtils.ellipsize(title, textPaint, (getXFrom(mTimeUpperBoundary) - drawingRect.left) - mReminderBitmap.getWidth(), TextUtils.TruncateAt.END);
            } else {
                txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - drawingRect.left) - mReminderBitmap.getWidth(), TextUtils.TruncateAt.END);
            }
            //txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - drawingRect.left) - 10, TextUtils.TruncateAt.END);
            canvas.drawText(txt, 0, txt.length(), drawingRect.left + 6, drawingRect.top, textPaint);
        }
    }

    private void drawNoInforEvent(final Canvas canvas, final int channelPosition, final Rect drawingRect) {
        //DLog.d("wujiang", "drawNoInforEvent: call this function");

        String title = "No Information";
        setEventDrawingRectangle(channelPosition, mTimeLowerBoundary - 1, mTimeUpperBoundary, drawingRect, title);

        // Background
        mPaint.setColor(drawCurrentTimeLine ? mEventLayoutBackgroundCurrent : mEventLayoutBackground);

        if (currentChannelPosition == channelPosition && mNeedDrawSelectedProgram) {
            mPaint.setColor(mEventLayoutSelected);
        }

        canvas.drawRect(drawingRect, mPaint);

        // Add left and right inner padding
        drawingRect.left += mChannelLayoutPadding;
        drawingRect.right -= mChannelLayoutPadding;

        // Text
        mPaint.setColor(mEventLayoutTextColor);
        mPaint.setTextSize(mEventLayoutTextSize);

        // Move drawing.top so text will be centered (text is drawn bottom>up)
        mPaint.getTextBounds(title, 0, title.length(), mMeasuringRect);
        drawingRect.top += (((drawingRect.bottom - drawingRect.top) / 2) + (mMeasuringRect.height() / 2));

        // Text
        /*if (currentChannelPosition == channelPosition && mNeedDrawSelectedProgram) {
            mPaint.setColor(Color.YELLOW);
            mPaint.setTextSize(mEventLayoutTextSize);
        } else {
            mPaint.setColor(mEventLayoutTextColor);
            mPaint.setTextSize(mEventLayoutTextSize);
        }*/

        //NEW CHANGES USING SHOW_TYPE of EVENT
        title = title.substring(0, mPaint.breakText(title, true, drawingRect.right - drawingRect.left, null));

        TextPaint textPaint = new TextPaint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(mPaint.getColor());
        textPaint.setTextSize(mPaint.getTextSize());
        textPaint.setStyle(mPaint.getStyle());
        textPaint.setTypeface(mPaint.getTypeface());

        CharSequence txt = null;
        txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - drawingRect.left) - 10, TextUtils.TruncateAt.END);
        canvas.drawText(txt, 0, txt.length(), drawingRect.left + 6, drawingRect.top, textPaint);
    }

    private void drawNoInforEventAheadFirstEvent(final Canvas canvas, final int channelPosition, final Rect drawingRect, Program firstEvent) {
        Log.d(TAG, "drawNoInforEventAheadFirstEvent: call this function");

        String title = "No Information";

        setEventDrawingRectangle(channelPosition, mTimeLowerBoundary - 1, firstEvent.getStartTimeUtcMillis() - 1, drawingRect, title);

        // Background
        mPaint.setColor(drawCurrentTimeLine ? mEventLayoutBackgroundCurrent : mEventLayoutBackground);

        canvas.drawRect(drawingRect, mPaint);

        // Text
        mPaint.setColor(mEventLayoutTextColor);
        mPaint.setTextSize(mEventLayoutTextSize);

        // Move drawing.top so text will be centered (text is drawn bottom>up)
        mPaint.getTextBounds(title, 0, title.length(), mMeasuringRect);
        drawingRect.top += (((drawingRect.bottom - drawingRect.top) / 2) + (mMeasuringRect.height() / 2));

        // Text
        /*if (currentChannelPosition == channelPosition && mNeedDrawSelectedProgram) {
            mPaint.setColor(Color.YELLOW);
            mPaint.setTextSize(mEventLayoutTextSize);
        } else {
            mPaint.setColor(mEventLayoutTextColor);
            mPaint.setTextSize(mEventLayoutTextSize);
        }*/

        //NEW CHANGES USING SHOW_TYPE of EVENT
        title = title.substring(0, mPaint.breakText(title, true, drawingRect.right - drawingRect.left, null));

        TextPaint textPaint = new TextPaint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(mPaint.getColor());
        textPaint.setTextSize(mPaint.getTextSize());
        textPaint.setStyle(mPaint.getStyle());
        textPaint.setTypeface(mPaint.getTypeface());

        CharSequence txt = null;
        txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - drawingRect.left) - 10, TextUtils.TruncateAt.END);
        canvas.drawText(txt, 0, txt.length(), drawingRect.left + 6, drawingRect.top, textPaint);
    }

    private void drawNoInforEventFollowingLastEvent(final Canvas canvas, final int channelPosition, final Rect drawingRect, Program lastEvent) {
        String title = "No Information";

        setEventDrawingRectangle(channelPosition, lastEvent.getEndTimeUtcMillis() + 1, mTimeUpperBoundary - 1, drawingRect, title);

        // Background
        mPaint.setColor(drawCurrentTimeLine ? mEventLayoutBackgroundCurrent : mEventLayoutBackground);

        canvas.drawRect(drawingRect, mPaint);

        // Text
        mPaint.setColor(mEventLayoutTextColor);
        mPaint.setTextSize(mEventLayoutTextSize);

        // Move drawing.top so text will be centered (text is drawn bottom>up)
        mPaint.getTextBounds(title, 0, title.length(), mMeasuringRect);
        drawingRect.top += (((drawingRect.bottom - drawingRect.top) / 2) + (mMeasuringRect.height() / 2));

        // Text
        /*if (currentChannelPosition == channelPosition && mNeedDrawSelectedProgram) {
            mPaint.setColor(Color.YELLOW);
            mPaint.setTextSize(mEventLayoutTextSize);
        } else {
            mPaint.setColor(mEventLayoutTextColor);
            mPaint.setTextSize(mEventLayoutTextSize);
        }*/

        //NEW CHANGES USING SHOW_TYPE of EVENT
        title = title.substring(0, mPaint.breakText(title, true, drawingRect.right - drawingRect.left, null));

        TextPaint textPaint = new TextPaint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(mPaint.getColor());
        textPaint.setTextSize(mPaint.getTextSize());
        textPaint.setStyle(mPaint.getStyle());
        textPaint.setTypeface(mPaint.getTypeface());

        CharSequence txt = null;
        txt = TextUtils.ellipsize(title, textPaint, (drawingRect.right - drawingRect.left) - 10, TextUtils.TruncateAt.END);
        canvas.drawText(txt, 0, txt.length(), drawingRect.left + 6, drawingRect.top, textPaint);
    }

    public void redraw() {
        //Log.d("wujiang", "redraw: call this fun");
        isAutoFocusChange = false;
        invalidate();
        requestLayout();
    }

    public void redraw(boolean autoFocusChange) {
        //Log.d("wujiang", "redraw: call this fun with autoFocusChange");
        isAutoFocusChange = autoFocusChange;
        invalidate();
        requestLayout();
    }

    void resetBoundaries() {
        mMillisPerPixel = calculateMillisPerPixel();
        mTimeOffset = calculatedBaseLine();
        mTimeLowerBoundary = getTimeFrom(0);
        mTimeUpperBoundary = getTimeFrom(getWidth());

        //Log.d("wujiang", "resetBoundaries: mMillisPerPixel = " + mMillisPerPixel);
        //Log.d("wujiang", "resetBoundaries: mTimeOffset = " + mTimeOffset);
        //Log.d("wujiang", "resetBoundaries: mTimeLowerBoundary = " + mTimeLowerBoundary);
        //Log.d("wujiang", "resetBoundaries: mTimeUpperBoundary = " + mTimeUpperBoundary);
        //Log.d("wujiang", "resetBoundaries: getWidth = " + getWidth());
    }

    public void setDrawNeedSelect(boolean select) {
        //Log.d("wujiang", "setDrawNeedSelect: select = " + select);
        mNeedDrawSelectedProgram = select;
    }

    public void setAdjustmentHeight(int height) {
        adjustment_height = height;
        //Log.d("wujiang", "setAdjustmentHeight: adjustment_height = " + adjustment_height);
    }

    public void setAdjustOnlyTimes(boolean enable) {
        adjust_only_one_time = enable;
        //Log.d("wujiang", "setAdjustmentHeight: adjust_only_one_time = " + adjust_only_one_time);
    }

    /**
     * @return :EpgView屏幕宽度当成2个小时,计算出每一毫秒占的像素值
     */
    private long calculateMillisPerPixel() {
        return HOURS_IN_VIEWPORT_MILLIS / (getResources().getDisplayMetrics().widthPixels - mChannelListViewWidth);
    }

    private Rect calculateProgramsHitArea() {
        mMeasuringRect.top = mTimeBarHeight;
        int visibleChannelsHeight = serviceList.size() * (mChannelLayoutHeight + mChannelLayoutMargin);
        mMeasuringRect.bottom = visibleChannelsHeight < getHeight() ? visibleChannelsHeight : getHeight();
        mMeasuringRect.left = mChannelLayoutWidth;
        mMeasuringRect.right = getWidth();
        return mMeasuringRect;
    }

    private long calculatedBaseLine() {
        long current_time_millis = mTvClock.currentTimeMillis();
        DateTime dateTime = new DateTime(current_time_millis).minusMillis(DAYS_BACK_MILLIS);
        if (DAYS_BACK_MILLIS > 0) {
            return dateTime.withTimeAtStartOfDay().getMillis();
        } else {
            return EPGUtils.roundOfTimeToLowerBound(dateTime.getMillis());
        }
    }

    private void calculateMaxVerticalScroll() {
        //final int maxVerticalScroll = getTopFrom(epgData.getChannelCount() - 2) + mChannelLayoutHeight;
        //mMaxVerticalScroll = maxVerticalScroll < getHeight() ? 0 : maxVerticalScroll - getHeight();
    }

    private void calculateMaxHorizontalScroll() {
        //mMaxHorizontalScroll = (int) (mChannelLayoutMargin + mChannelLayoutWidth + (DAYS_BACK_MILLIS + EPGUtils.roundOfTimeToLowerBound(LocalDateTime.now().toDateTime().getMillis()) + HOURS_IN_VIEWPORT_MILLIS + ((LocalDateTime.now().toDateTime().withTime(23, 59, 59, 999).getMillis())) + DAYS_FORWARD_MILLIS) / mMillisPerPixel);
    }

    private int getXPositionStart() {
        if (DAYS_BACK_MILLIS > 0) {
            int temp = getXFrom(EPGUtils.roundOfTimeToLowerBound(mTvClock.currentTimeMillis())) - mChannelLayoutMargin - mChannelLayoutWidth;
            return temp;
        } else {
            return 0;
        }
    }

    /**
     * 设置要显示的节目
     * @param context
     * @param channelArrayList
     * @param fragment
     * @param default_position
     */
    public void setChannelsData(Context context, ArrayList<Channel> channelArrayList, EpgMenuFragment fragment, int default_position) {
        contextObj = context;
        serviceList = channelArrayList;
        mEpgMenuFragment = fragment;
        isAutoFocusChange = false;

        Log.d(TAG, "EpgView: setChannelsData->serviceList size = " + serviceList.size());

        if (serviceList.size() == 0) {
            return;
        }

        currentChannelPosition = default_position;
        //Log.d("wujiang", "EpgView: currentChannelPosition = " + currentChannelPosition);

        int programPosition = getProgramPositionNew(currentChannelPosition, mTvClock.currentTimeMillis());
        currentProgram = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(currentChannelPosition).getId(), programPosition);

        //Log.d("wujiang", "setEPGData: programPosition = " + programPosition);
        //Log.d("wujiang", "setEPGData: channel name = " + serviceList.get(currentChannelPosition).getDisplayName());

        if (currentProgram == null) {
            Log.d(TAG, "setChannelsData: currentProgram == null");
        }

        //暂时不设置被选中的event
        //if (currentProgram != null) {
        //    currentProgram.setSelected(true);
        //}

        referenceEventPosition = programPosition;
        //Log.e(TAG, " * * * EPG setEPGData() (referenceEventPosition) " + referenceEventPosition);
        referenceEvent = currentProgram;
    }

    /**
     * @param x 像素值(某个地方距epg起始地方的偏移值)
     * @return 得到x地方的时刻,单位毫秒
     */
    private long getTimeFrom(int x) {
        return (x * mMillisPerPixel) + mTimeOffset;
    }

    /**
     * @param position 可以理解成第几个节目
     * @return 第position节目显示区域顶部位置像素值
     */
    private int getTopFrom(int position) {
        int y = position * (mChannelLayoutHeight + mChannelLayoutMargin) + mChannelLayoutMargin + mTimeBarHeight;
        return y;
    }

    private int getXFrom(long time) {
        return (int) ((time - mTimeOffset) / mMillisPerPixel) + mChannelLayoutMargin + mChannelLayoutWidth + mChannelLayoutMargin;
    }

    private boolean isEventStartTimeVisible(final long start) {
        return (start >= mTimeLowerBoundary || start <= mTimeUpperBoundary);
    }

    /**
     * 在节目对应的EPG信息列表中查找起始时间和结束时间在time之间的那个EPG
     * @param channelPosition
     * @param time
     * @return
     */
    private int getProgramPositionNew(int channelPosition, long time) {
        List<Program> events = ProgramsDataManager.getInstance().getChannelProgramsByChannelId(serviceList.get(channelPosition).getId());
        //Log.d("wujiang", "getProgramPositionNew 1: events size = " + events.size());
        //Log.d("wujiang", "getProgramPositionNew 1: time = " + time);

        if (events != null) {
            for (int eventPos = 0; eventPos < events.size(); eventPos++) {
                Program event = events.get(eventPos);
                //Log.d("wujiang", "getProgramPositionNew 1: event getIntStartTime = " + event.getStartTimeUtcMillis());
                //Log.d("wujiang", "getProgramPositionNew 1: event getIntStopTime = " + event.getEndTimeUtcMillis());
                //Log.d("wujiang", "getProgramPositionNew 1: event title = " + event.getTitle());

                if ((event.getStartTimeUtcMillis()) <= time && ((event.getEndTimeUtcMillis()) > time)) {
                    return eventPos;
                }
            }
        }
        return -1;
    }

    /**
     * 在节目对应的EPG信息列表中查找时间在time和time1之间的那个EPG
     * @param channelPosition
     * @param time
     * @param time1
     * @return
     */
    private int getProgramPositionNew(int channelPosition, long time, long time1) {
        Log.d("wujiang", "getProgramPositionNew: channel id = " + serviceList.get(channelPosition).getId());
        List<Program> events = ProgramsDataManager.getInstance().getChannelProgramsByChannelId(serviceList.get(channelPosition).getId());

        if (events != null) {
            //Log.d("wujiang", "getProgramPositionNew: events size = " + events.size());
            for (int eventPos = 0; eventPos < events.size(); eventPos++) {
                Program event = events.get(eventPos);
                //DLog.d("wujiang", "getProgramPositionNew: event getIntStartTime = " + event.getIntStartTime());
                //DLog.d("wujiang", "getProgramPositionNew: event getIntStopTime = " + event.getIntStopTime());

                if (event.getEndTimeUtcMillis() > time && event.getStartTimeUtcMillis() < time1) {
                    return eventPos;
                }
            }
        }
        return -1;
    }

    public void moveToDestPosition(int position, int direction) {
        //Log.d("wujiang", "moveToDestPosition: position = " + position);
        if (currentProgram != null) {
            //Log.d("wujiang", "moveToDestPosition: 1111111111");
            isMoveRight = false;
            isMoveLeft = false;
            isAutoFocusChange = false;

            long time = 0;

            if (referenceEvent.getStartTimeUtcMillis() < mTimeOffset) {
                time = mTimeOffset + 1;
            } else if (isCurrent(referenceEvent) && referenceEvent.getStartTimeUtcMillis() < EPGUtils.roundOfTimeToLowerBound(mTvClock.currentTimeMillis())) {
                time = EPGUtils.roundOfTimeToLowerBound(mTvClock.currentTimeMillis()) + 1;
            } else {
                time = EPGUtils.roundOfTimeToLowerBound(referenceEvent.getStartTimeUtcMillis()) + 1;
            }

            //gotoProgram(position, time);
            //这里取代了gotoProgram,是因为挨着的两天如果有一天没EPG时,上下切换节目时有的时候会找不到event,所以就在mTimeLowerBoundary到mTimeUpperBoundary之间找
            gotoProgramBetweenTimeNew(position, mTimeLowerBoundary, mTimeUpperBoundary);
            invalidate();

            int y = getTopFrom(position);

            if (direction == UP_DIRECTION) {
                int programAreaY = calculateProgramsHitArea().top + getScrollY();
                if (y < programAreaY) {
                    scrollBy(0, y - programAreaY);
                }
            } else {
                //总的channel个数大于MaxItemCountInOnePage个才允许去滑动
                //Log.d("wujiang", "moveToDestPosition: position = " + position);
                if (position >= MaxItemCountInOnePage) {
                    int programAreaY = calculateProgramsHitArea().bottom + getScrollY() - mChannelLayoutHeight;
                    if (y > programAreaY) {
                        scrollBy(0, y - programAreaY);
                    }
                }
            }
        }
    }


    /**
     * 获取当前event的上一个event
     *
     * @param channelPosition       当前channel的位置
     * @param programmeBeanProgram  焦点所在的event
     * @return                      true:找到了上一个event; false:没有找到上一个event
     */
    private boolean gotoAheadProgramNew(int channelPosition, Program programmeBeanProgram) {
        int current_index = ProgramsDataManager.getInstance().getPositionOfProgram(serviceList.get(channelPosition).getId(), programmeBeanProgram);
        //Log.d("wujiang", "gotoAheadProgramNew: current_index = " + current_index);
        //Log.d("wujiang", "gotoAheadProgramNew: current programme name = " + programmeBeanrogram.getTitle());
        //Log.d("wujiang", "gotoAheadProgramNew: current programme start = " + programmeBeanrogram.getStart());

        isMoveRight = false;
        isMoveLeft = true;

        if (current_index != -1) {
            if (current_index == 0 || programmeBeanProgram.getStartTimeUtcMillis() <= mTimeOffset) {
                Log.d(TAG, "gotoAheadProgramNew: It is the first event.");
                return false;
            }

            ProgramsDataManager.getInstance().clearSelection();
            Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), current_index - 1);

            if (event.getEndTimeUtcMillis() <= mTimeOffset) {
                Log.d(TAG, "gotoAheadProgramNew: It is the lowest time event");
                return false;
            }

            event.setSelected(true);
            currentProgram = event;

            //Log.d("wujiang", "gotoAheadProgramNew: currentProgramname = " + currentProgram.getTitle());
            //Log.d("wujiang", "gotoAheadProgramNew: currentProgram start = " + currentProgram.getStart());

            if (isMoveLeft || isMoveRight) {
                referenceEvent = event;
            } else {
                referenceEventPosition = current_index - 1;
            }

            Log.d(TAG, "gotoAheadProgramNew: Find a event in epg data");
            return true;
        }

        int program_count = ProgramsDataManager.getInstance().getProgramCountOfChannel(serviceList.get(channelPosition).getId());
        if (program_count > 0) {
            for (int i = program_count - 1; i >= 0; i--) {
                Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), i);
                if (event.getEndTimeUtcMillis() <= mTimeLowerBoundary && event.getStartTimeUtcMillis() >= mTimeOffset) {
                    ProgramsDataManager.getInstance().clearSelection();

                    event.setSelected(true);
                    currentProgram = event;

                    //Log.d("wujiang", "gotoAheadProgramNew: currentProgramname = " + currentProgram.getTitle());
                    //Log.d("wujiang", "gotoAheadProgramNew: currentProgram start = " + currentProgram.getStart());

                    if (isMoveLeft || isMoveRight) {
                        referenceEvent = event;
                    } else {
                        referenceEventPosition = i;
                    }

                    return true;
                }
            }
        }

        Log.d(TAG, "gotoAheadProgramNew: Don't find any event");
        return false;
    }

    /**
     * 获取当前event的下一个event
     *
     * @param channelPosition           当前channel
     * @param programmeBeanProgram      焦点所在的event
     * @return                          true:找到了下一个event; false:没有找到下一个event
     */
    private boolean gotoNextProgramNew(int channelPosition, Program programmeBeanProgram) {
        int current_index = ProgramsDataManager.getInstance().getPositionOfProgram(serviceList.get(channelPosition).getId(), programmeBeanProgram);
        //Log.d("wujiang", "gotoNextProgramNew: current_index = " + current_index);
        //Log.d("wujiang", "gotoNextProgramNew: current programme name = " + programmeBeanrogram.getTitle());
        //Log.d("wujiang", "gotoNextProgramNew: current programme start = " + programmeBeanrogram.getStartTimeUtcMillis());

        isMoveRight = true;
        isMoveLeft = false;

        if (current_index != -1) {
            if (current_index >= (ProgramsDataManager.getInstance().getProgramCountOfChannel(serviceList.get(channelPosition).getId()) - 1)) {
                Log.d(TAG, "gotoNextProgramNew: It is the last event");
                return false;
            }

            ProgramsDataManager.getInstance().clearSelection();

            Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), current_index + 1);
            event.setSelected(true);
            currentProgram = event;

            //Log.d("wujiang", "gotoNextProgramNew: found next program = " + currentProgram.getTitle());
            //Log.d("wujiang", "gotoNextProgramNew: next program start = " + currentProgram.getStartTimeUtcMillis());

            if (isMoveLeft || isMoveRight) {
                referenceEvent = event;
            } else {
                referenceEventPosition = current_index + 1;
            }

            Log.d(TAG, "gotoNextProgramNew: Find a event in epg data");
            return true;
        }

        int program_count = ProgramsDataManager.getInstance().getProgramCountOfChannel(serviceList.get(channelPosition).getId());
        Log.d(TAG, "gotoNextProgramNew: program_count = " + program_count);
        if (program_count > 0) {
            for (int i = 0; i < program_count; i++) {
                Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), i);
                if (event.getStartTimeUtcMillis() >= mTimeUpperBoundary) {
                    ProgramsDataManager.getInstance().clearSelection();

                    event.setSelected(true);
                    currentProgram = event;

                    //Log.d("wujiang", "gotoNextProgramNew: currentProgramname = " + currentProgram.getTitle());
                    //Log.d("wujiang", "gotoNextProgramNew: currentProgram start = " + currentProgram.getStartTimeUtcMillis());

                    if (isMoveLeft || isMoveRight) {
                        referenceEvent = event;
                    } else {
                        referenceEventPosition = i;
                    }

                    return true;
                }
            }
        }

        Log.d(TAG, "gotoNextProgramNew: Don't find any event");
        return false;
    }

    /**
     * 去找特定的event
     *
     * @param channelPosition   被找的节目索引
     * @param time              显示屏最左侧时刻
     * @param time1             显示屏最右侧时刻
     * @return                  找到的event,没找到返回null
     */
    private Program gotoProgramBetweenTimeNew(int channelPosition, long time, long time1) {
        if (channelPosition > -1 && channelPosition < serviceList.size()) {
            int programPosition = getProgramPositionNew(channelPosition, time, time1);

            if (programPosition != -1) {
                //Log.d("wujiang", "gotoProgramBetweenTimeNew: 11111111111");
                ProgramsDataManager.getInstance().clearSelection();
                Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), programPosition);

                event.setSelected(true);
                currentProgram = event;
                currentChannelPosition = channelPosition;

                if (isMoveLeft || isMoveRight) {
                    referenceEvent = event;
                } else {
                    referenceEventPosition = programPosition;
                }
                return event;
            } else {
                //Log.d("wujiang", "gotoProgramBetweenTimeNew: 22222222222222");
                //没有EPG信息
                ProgramsDataManager.getInstance().clearSelection();
                Program event = new Program();

                event.setTitle("No Information");
                event.setStartTimeUtcMillis(mTimeLowerBoundary);
                event.setEndTimeUtcMillis(mTimeUpperBoundary);
                event.setSelected(true);

                currentProgram = event;
                currentChannelPosition = channelPosition;

                //这里没有判断是否是左右切换
                referenceEvent = event;
                return event;
            }
        }
        return null;
    }

    private Program gotoProgramNotSelectNew(int channelPosition, long time) {
        Log.d(TAG, "gotoProgramNotSelectNew: call this fun, channelPosition = " + channelPosition);
        if (channelPosition > -1 && channelPosition < serviceList.size()) {
            int programPosition = getProgramPositionNew(channelPosition, time);
            Log.d(TAG, "gotoProgramNotSelectNew: programPosition = " + programPosition);

            if (programPosition != -1) {
                ProgramsDataManager.getInstance().clearSelection();
                Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(channelPosition).getId(), programPosition);
                event.setSelected(true);

                currentProgram = event;
                currentChannelPosition = channelPosition;

                if (isMoveLeft || isMoveRight) {
                    referenceEvent = event;
                } else {
                    referenceEventPosition = programPosition;
                }
                return event;
            }
        }
        return null;
    }

    /**
     *
     * @return
     *     false: 再也找不到后续event了,即当前event是最后一个event
     *     true: 找到了后续的event
     */
    public boolean moveRight() {
        if (currentProgram != null) {
            isAutoFocusChange = false;

            if (!gotoNextProgramNew(currentChannelPosition, currentProgram)) {
                return false;
            }

            /**
             * 调用了上面的gotoNextProgram就不需要调用gotoProgram了
             isMoveRight = true;
             isMoveLeft = false;

             gotoProgram(currentChannelPosition, currentProgram.getIntStopTime() + 1);
             */
            invalidate();

            int x = 0;

            long dur = currentProgram.getEndTimeUtcMillis() - currentProgram.getStartTimeUtcMillis();
            long currentUpperBound = EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()) + HOURS_IN_VIEWPORT_MILLIS;
            if (dur > HOURS_IN_VIEWPORT_MILLIS && !isEventStartTimeVisible(currentProgram.getStartTimeUtcMillis())) {
                //Log.d("wujiang", "moveRight: 1111111111111111");
                if (EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()) != currentProgram.getStartTimeUtcMillis()) {
                    x = getXFrom(currentProgram.getStartTimeUtcMillis() + ((30 * 60 * 1000) - (currentProgram.getStartTimeUtcMillis() - EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()))));
                } else {
                    x = getXFrom(currentProgram.getStartTimeUtcMillis() + (HOURS_IN_VIEWPORT_MILLIS - 30 * 60 * 1000));
                }
                //x = getXFrom((currentProgram.getStart() - EPGUtils.roundOfTimeToLowerBound(currentProgram.getStart())) + HOURS_IN_VIEWPORT_MILLIS);
            } else if (dur > HOURS_IN_VIEWPORT_MILLIS) {
                //Log.d("wujiang", "moveRight: 22222222222");
                if (EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()) != currentProgram.getStartTimeUtcMillis()) {
                    x = getXFrom(currentProgram.getStartTimeUtcMillis() + ((60 * 60 * 1000) - ((currentProgram.getStartTimeUtcMillis() - EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis())))));
                } else {
                    x = getXFrom(currentProgram.getStartTimeUtcMillis() + (HOURS_IN_VIEWPORT_MILLIS - 60 * 60 * 1000));
                }
            } else if (EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()) != currentProgram.getStartTimeUtcMillis()) {
                //Log.d("wujiang", "moveRight: 33333333333333");
                x = getXFrom(currentProgram.getStartTimeUtcMillis() + ((30 * 60 * 1000) - (currentProgram.getStartTimeUtcMillis() - EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()))));
            } else {
                //Log.d("wujiang", "moveRight: 444444444444444");
                x = getXFrom(currentProgram.getStartTimeUtcMillis() + (30 * 60 * 1000));
            }

            //EPGUtils.printTime("MoveRight", mTimeUpperBoundary);
            //int x = getXFrom(currentProgram.getEndTime());

            //Log.d("wujiang", "moveRight: x = " + x);
            //Log.d("wujiang", "moveRight: getScrollX() = " + getScrollX());
            //Log.d("wujiang", "moveRight: getWidth() = " + getWidth());
            int programAreaX = calculateProgramsHitArea().right + getScrollX();

            //Log.d("wujiang", "moveRight: calculateProgramsHitArea().right = " + calculateProgramsHitArea().right);

            if (x > programAreaX) {
                scrollBy(x - programAreaX, 0);
            }

            Log.d(TAG, "moveRight: call end");
            return true;
        }
        return false;
    }

    public boolean moveLeft() {
        if (currentProgram != null) {
            isAutoFocusChange = false;

            /*if (currentProgram.getStartTimeUtcMillis() <= mTimeOffset) {
                //Log.d("wujiang", "moveLeft: 1111111");
                return false;
            }

            isMoveRight = false;
            isMoveLeft = true;
            gotoProgram(currentChannelPosition, currentProgram.getStartTimeUtcMillis() - 1);*/

            if (!gotoAheadProgramNew(currentChannelPosition, currentProgram)) {
                return false;
            }

            invalidate();
            int x = 0;//getXFrom(currentProgram.getStartTime());
            if (currentProgram.getStartTimeUtcMillis() < mTimeOffset) {
                x = getXFrom(mTimeOffset);
            } else {
                x = getXFrom(EPGUtils.roundOfTimeToLowerBound(currentProgram.getStartTimeUtcMillis()));
            }

            int programAreaX = calculateProgramsHitArea().left + getScrollX();
            if (x < programAreaX) {
                scrollBy(x - programAreaX, 0);
            }
        } else {
            Log.e(TAG, " @ MoveLeft @ I think CatchUp is enabled");
            return false;
        }

        return true;
    }

    /**
     * 显示上一天开始的epg信息
     */
    public void moveToPrevDay() {
        if (serviceList == null || serviceList.size() <= 0) {
            return;
        }

        //当前页所在天的0点时刻
        long currentPageDayStartTime = EPGUtils.getDayStartTime(getTimeLowerBoundary());

        //当前页所在的天的上一天的0点时刻
        long prevDayStartTime = currentPageDayStartTime - EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;
        //Log.d("wujiang", "prevDayStartTime = " + prevDayStartTime);

        if (prevDayStartTime <= mTimeOffset) {
            prevDayStartTime = mTimeOffset;
        }

        referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, prevDayStartTime, prevDayStartTime + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND);

        int scrollX = getXFrom(prevDayStartTime);
        scrollTo(scrollX, getScrollY());
    }

    /**
     * 显示下一天开始的epg信息
     */
    public void moveToNextDay() {
        if (serviceList == null || serviceList.size() <= 0) {
            return;
        }

        //当天的凌晨0点时间
        long firstDayStartTime = EPGUtils.getDayStartTime(mTimeOffset);

        //当前页EPG所属Day的0点时刻
        long currentPageDayStartTime = EPGUtils.getDayStartTime(getTimeLowerBoundary());

        //当前页晚上24点时刻
        long currentPageEndTime = currentPageDayStartTime + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;
        //Log.d("wujiang", "nextDayStartTime = " + currentPageEndTime);

        if ((currentPageEndTime / 1000 - firstDayStartTime / 1000) >= 7 * EPGUtils.DAY_OF_SECONDS) {
            PlayerUtil.showToast("It’s the last day!", 1000);
            return;
        }

        long queryEndTime = currentPageEndTime + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND;
        //Log.d("wujiang", "queryEndTime = " + queryEndTime);

        ArrayList<Long> queryChannelsArrayList = new ArrayList<>();
        int firstVisibleChannelPosition = getFirstVisibleChannelPosition();
        int lastVisibleChannelPosition = getLastVisibleChannelPosition();

        for (int pos = firstVisibleChannelPosition; pos <= lastVisibleChannelPosition; pos++) {
            Channel channel = serviceList.get(pos);
            //Log.d("wujiang", "moveToNextDay: channel name = " + channel.getDisplayName());
            queryChannelsArrayList.add(channel.getId());
        }

        referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, currentPageEndTime, currentPageEndTime + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND);
        //Log.d("wujiang", "moveToNextDay: referenceEvent startTime = " + referenceEvent.getStartTimeUtcMillis());
        //Log.d("wujiang", "moveToNextDay: referenceEvent endTime = " + referenceEvent.getEndTimeUtcMillis());

        int scrollX = getXFrom(currentPageEndTime);
        scrollTo(scrollX, getScrollY());

        ProgramsDataManager.getInstance().queryChannelsProgramFromDatabase(firstDayStartTime, queryEndTime, queryChannelsArrayList, 0);
    }

    /**
     * 显示当天开始的epg信息
     */
    public void moveToToday() {
        if (serviceList == null || serviceList.size() <= 0) {
            return;
        }

        referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeOffset, mTimeOffset + EPGUtils.DAY_OF_SECONDS * EPGUtils.ONE_THOUSAND);
        int scrollX = getXFrom(mTimeOffset);
        scrollTo(scrollX, getScrollY());
    }

    public long getTimeLowerBoundary() {
        return mTimeLowerBoundary;
    }

    public long getTimeUpperBoundary() {
        return mTimeUpperBoundary;
    }

    public long getTimeOffset () {
        return mTimeOffset;
    }

    /**
     * This will recalculate boundaries, maximal scroll and scroll to start position which is current time.
     * To be used on device rotation etc since the device height and width will change.
     *
     * @param withAnimation
     * @param default_position 默认位置,也就是第一行显示正在播放节目的EPG.
     * @param needDrawSelect   是否要画焦点状态
     */
    public void recalculateAndRedraw(boolean withAnimation, int default_position, boolean needDrawSelect) {
        mNeedDrawSelectedProgram = needDrawSelect;
        //Log.d("wujiang", "recalculateAndRedraw: default_position = " + default_position);
        //Log.d("wujiang", "recalculateAndRedraw: mNeedDrawSelectedProgram = " + mNeedDrawSelectedProgram);

        if (serviceList != null && serviceList.size() > 0) {
            resetBoundaries();
            calculateMaxVerticalScroll();
            calculateMaxHorizontalScroll();

            //注意:这里需要微调
            //本意是只调用下getTopFrom(default_position)就行了的,但实际效果不是,于是经多次调试得出先减去mChannelLayoutHeight再减去19。
            mScroller.startScroll(getScrollX(), getScrollY(), getXPositionStart() - getScrollX(), getTopFrom(default_position) - mChannelLayoutHeight - 19, withAnimation ? 0 : 0);

            if (currentProgram == null) {
                referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeLowerBoundary, mTimeUpperBoundary);
            } else {
                if ((currentProgram.getStartTimeUtcMillis() > EPGUtils.getCurrentTimeInMillis()) || (currentProgram.getStartTimeUtcMillis() < EPGUtils.getCurrentTimeInMillis())) {
                    int eventPosition = getProgramPositionNew(currentChannelPosition, EPGUtils.getCurrentTimeInMillis());
                    Program event = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(currentChannelPosition).getId(), eventPosition);
                    if (mNeedDrawSelectedProgram == false) {
                        referenceEvent = gotoProgramNotSelectNew(currentChannelPosition, event.getStartTimeUtcMillis());
                    } else {
                        referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeLowerBoundary, mTimeUpperBoundary);
                    }
                }
            }
        } else {
            Log.d(TAG, "recalculateAndRedraw: serviceList size = 0");
            resetBoundaries();
        }

        redraw();
    }

    /**
     * 查找到将要获取到焦点的program
     * 判断position的理由:
     *      当上下切换节目后,立马向右切换program,会看到焦点里面又回到了第一个program上。
     *      即如果referenceEvent是一个真实存在的program,即不需要再去找即将要获取焦点的program。
     */
    public void findBeSelectedProgram() {
        if (referenceEvent == null) {
            referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeLowerBoundary, mTimeUpperBoundary);
            return;
        }

        int position = ProgramsDataManager.getInstance().getPositionOfProgram(serviceList.get(currentChannelPosition).getId(), referenceEvent);
        Log.d(TAG, "findBeSelectedProgram: position = " + position);

        if (position == -1) {
            referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeLowerBoundary, mTimeUpperBoundary);
            return;
        } else {
            /**
             * 当找到了referenceEvent,如果这个referenceEvent的结束时间超过了当天的24点,那么它就被认为是可见的event。
             * 当在这种event获取焦点时按绿色键请求下一天的epg后,需要用它的位置获取到该channel新的program list里对应的那个event(老的program list已经被clear了),
             * 并设置它为选中状态。不这样做的话,当请求完下一天的epg后,焦点会变的没有。
             **/
            referenceEvent = ProgramsDataManager.getInstance().getProgramOfChannelByPosition(serviceList.get(currentChannelPosition).getId(), position);
            referenceEvent.setSelected(true);
        }

        //Log.d("wujiang", "findBeSelectedProgram: referenceEvent start time = " + referenceEvent.getStartTimeUtcMillis());
        //Log.d("wujiang", "findBeSelectedProgram: referenceEvent end time = " + referenceEvent.getEndTimeUtcMillis());
        //Log.d("wujiang", "findBeSelectedProgram: referenceEvent title = " + referenceEvent.getTitle());

        if (isEventVisible(referenceEvent.getStartTimeUtcMillis(), referenceEvent.getEndTimeUtcMillis())) {
            Log.d(TAG, "findBeSelectedProgram: referenceEvent is visible");
            return;
        } else {
            Log.d(TAG, "findBeSelectedProgram: referenceEvent is invisible");
            referenceEvent = gotoProgramBetweenTimeNew(currentChannelPosition, mTimeLowerBoundary, mTimeUpperBoundary);
        }
    }

    private boolean isCurrent(Program program) {
        if (program == null) {
            return false;
        }

        long now = mTvClock.currentTimeMillis();
        return now >= (program.getStartTimeUtcMillis()) && now <= (program.getEndTimeUtcMillis());
    }
}

//EpgView的部分资源文件
<dimen name="epg_line_space">1dp</dimen>
<dimen name="epg_event_space">1dp</dimen>
<dimen name="listviews_space">1dp</dimen>
<dimen name="epg_time_line_width">1dp</dimen>
<dimen name="zero">0dp</dimen>
<dimen name="epg_view_animator_width">2138dp</dimen>
<dimen name="epg_group_listview_width">109dp</dimen>
<dimen name="epg_channel_listview_width">162dp</dimen>
<dimen name="epg_view_width">688dp</dimen>
<dimen name="epg_view_large_width">797dp</dimen>
<dimen name="epg_event_text_size">@dimen/px_26.0</dimen>
<dimen name="epg_time_bar_text_size">@dimen/px_25.0</dimen>
<dimen name="epg_help_bar_height">71px</dimen>
<dimen name="dp_16">16dp</dimen>

  大部分的重要的代码都贴上了,希望对你有帮助!

                                                THE      END

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值