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(@