Java tutorial
/* * Copyright 2014 Nicholas Liu * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ca.zadrox.dota2esportticker.ui; import android.app.LoaderManager; import android.content.Loader; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.support.v4.view.ViewCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.widget.Toolbar; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import ca.zadrox.dota2esportticker.R; import ca.zadrox.dota2esportticker.data.gson.model.DotaGameData; import ca.zadrox.dota2esportticker.data.gson.model.FinishedGame; import ca.zadrox.dota2esportticker.data.gson.model.Game; import ca.zadrox.dota2esportticker.ui.widget.ObservableScrollView; import ca.zadrox.dota2esportticker.util.DotaMapModel; import ca.zadrox.dota2esportticker.util.LogUtils; import ca.zadrox.dota2esportticker.util.PrefUtils; import ca.zadrox.dota2esportticker.util.StringUtils; import ca.zadrox.dota2esportticker.util.UIUtils; import ca.zadrox.dota2esportticker.util.steamapi.SAPIGameResultLoader; import ca.zadrox.dota2esportticker.util.steamapi.SAPISingleLiveGameLoader; import ca.zadrox.dota2esportticker.util.xform.CropImageTransform; interface LiveContentView { void initPlayerMapIcon(int playerSlot, int heroId); void setViewVisible(int resId, boolean isVisible); void updatePlayerMapIcon(int playerSlot, float x, float y, int state); void initPlayerView(int playerSlot, String name); void setPlayerKDA(int playerSlot, int k, int d, int a); void setPlayerCS(int playerSlot, int lh, int d); void setPlayerPortrait(int playerSlot, int resId); void setPlayerItem(int playerSlot, int itemSlot, int resId); void setPlayerDesc(int playerSlot, int playerLevel, String heroName); void setGameTime(String gameTime); void setGameScores(int radiantScore, int direScore); void setTeamHeaders(String radiantName, String direName); void addTick(int resId, String name, String details, int team); void addTickSeparator(String time); } /** * Created by Acco on 11/3/2014. */ public class LiveGameStatsActivity extends BaseActivity implements ObservableScrollView.Callbacks, LoaderManager.LoaderCallbacks<DotaGameData>, LiveContentView { private static final String TAG = LiveGameStatsActivity.class.getSimpleName(); private static final float PHOTO_ASPECT_RATIO = 1.5f; private static final int GAME_UPDATE_INTERVAL = 20000; // 20s public static String ARGS_GAME_TNL = "team_name_left"; public static String ARGS_GAME_TNR = "team_name_right"; public static String ARGS_GAME_TLL = "team_logo_left"; public static String ARGS_GAME_TLR = "team_logo_right"; public static String ARGS_GAME_TN = "tournament_name"; public static String ARGS_GAME_BO = "best_of"; public static String ARGS_GAME_MID = "match_id"; private final int[] mPlayerMiniMapIcons = new int[] { R.id.radiant_player_one, R.id.radiant_player_two, R.id.radiant_player_three, R.id.radiant_player_four, R.id.radiant_player_five, R.id.dire_player_one, R.id.dire_player_two, R.id.dire_player_three, R.id.dire_player_four, R.id.dire_player_five }; private final int[] itemViewIds = new int[] { R.id.player_item_one, R.id.player_item_two, R.id.player_item_three, R.id.player_item_four, R.id.player_item_five, R.id.player_item_six }; private Handler mHandler = new Handler(); private View[] mPlayerLayouts = new View[10]; private double gameTime; private String mUrl; private String mMatchScore; private boolean mStarred; private boolean mInitStarred; private MenuItem mShareMenuItem; private View mScrollViewChild; private TextView mTitle; private TextView mSubtitle; private Callback mLoadMapCallback = new Callback() { @Override public void onSuccess() { mDetailsContainer.animate().alpha(1).translationY(0).setDuration(400).start(); } @Override public void onError() { } }; private ObservableScrollView mScrollView; private ImageView mapView; private View mHeaderBox; private View mDetailsContainer; private int mPhotoHeightPixels; private int mHeaderHeightPixels; private int mapX; private int mapY; private long matchId; private boolean mHasPhoto; private View mGameViewContainer; private ImageView mGameImageView; private TextView mGameScoreViewLeft; private TextView mGameScoreViewRight; private TextView mGameTimeView; private ProgressBar mUpdateProgressBar; private ProgressBarAnimation mUpdateProgressAnim; private boolean gameCompleted = false; private static final String KEY_GAME_COMPLETE = "game_complete"; private LinearLayout tickerLayout; private DrawerLayout mDrawerLayout; private Runnable mGameUpdaterRunnable = new Runnable() { @Override public void run() { Loader loader = getLoaderManager().getLoader(0); if (loader != null) loader.forceLoad(); } }; private Runnable mGameResultRunnable = new Runnable() { @Override public void run() { Loader loader = getLoaderManager().getLoader(1); if (loader != null) loader.forceLoad(); } }; private LiveTickerAdapter mLiveTickerAdapter; private ListView mDrawerListView; private float mMaxHeaderElevation; private LiveContentPresenter mPresenter; private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { recomputePhotoAndScrollingMetrics(); } }; @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_ticker: mDrawerLayout.openDrawer(Gravity.END); } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.live, menu); return super.onCreateOptionsMenu(menu); } private void setBundleContents() { final Toolbar toolbar = getActionBarToolbar(); mHandler.post(new Runnable() { @Override public void run() { mTitle.setText(getIntent().getStringExtra(ARGS_GAME_TNL) + " vs " + getIntent().getStringExtra(ARGS_GAME_TNR)); mSubtitle.setText(getIntent().getStringExtra(ARGS_GAME_TN) + " - " + getIntent().getStringExtra(ARGS_GAME_BO)); toolbar.setTitle(""); } }); toolbar.setNavigationIcon(R.drawable.ic_up); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); matchId = getIntent().getLongExtra(ARGS_GAME_MID, 1); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_live_stats); mMaxHeaderElevation = getResources().getDimensionPixelSize(R.dimen.session_detail_max_header_elevation); mScrollView = (ObservableScrollView) findViewById(R.id.game_scroll_view); mScrollView.addCallbacks(this); ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { vto.addOnGlobalLayoutListener(mGlobalLayoutListener); } mScrollViewChild = findViewById(R.id.game_scroll_view_child); mDetailsContainer = findViewById(R.id.game_details_container); mDetailsContainer.setAlpha(0); mDetailsContainer.setY(1200); mHeaderBox = findViewById(R.id.header_game); mGameViewContainer = findViewById(R.id.game_photo_container); mGameImageView = (ImageView) findViewById(R.id.game_photo); mGameTimeView = (TextView) findViewById(R.id.game_time_view); mGameScoreViewLeft = (TextView) findViewById(R.id.game_score_view_left); mGameScoreViewRight = (TextView) findViewById(R.id.game_score_view_right); tickerLayout = (LinearLayout) findViewById(R.id.sidedrawer); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mTitle = (TextView) findViewById(R.id.header_title); mSubtitle = (TextView) findViewById(R.id.header_subtitle); mUpdateProgressBar = (ProgressBar) findViewById(R.id.header_progressbar); mapView = (ImageView) findViewById(R.id.game_map_view); mHasPhoto = true; mGameImageView.setColorFilter(Color.rgb(123, 123, 123), android.graphics.PorterDuff.Mode.MULTIPLY); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); Picasso.with(this).load(R.drawable.drawable_dota_bg_dire_ancient).config(Bitmap.Config.ARGB_8888) .resize(Math.round(displayMetrics.widthPixels * PHOTO_ASPECT_RATIO * 2 / 3), displayMetrics.widthPixels * 2 / 3) .transform(new CropImageTransform(displayMetrics.heightPixels, displayMetrics.widthPixels)) .into(mGameImageView); setBundleContents(); initMapView(); mPresenter = new LiveStatsPresenter(this); mLiveTickerAdapter = new LiveTickerAdapter(this, R.layout.sidedrawer_list_item); if (savedInstanceState != null) { mLiveTickerAdapter.onRestoreInstanceState(savedInstanceState); gameCompleted = savedInstanceState.getBoolean(KEY_GAME_COMPLETE, false); } mDrawerListView = (ListView) findViewById(R.id.sidedrawer_items_list); mDrawerListView.setAdapter(mLiveTickerAdapter); mUpdateProgressAnim = new ProgressBarAnimation(mUpdateProgressBar, 2000, 0); mUpdateProgressAnim.setDuration(GAME_UPDATE_INTERVAL); mUpdateProgressAnim.setInterpolator(new LinearInterpolator()); if (!PrefUtils.hasLiveDrawerBeenShown(this)) { mDrawerLayout.openDrawer(Gravity.END); Toast.makeText(this, "Match events are shown in the right drawer", Toast.LENGTH_SHORT).show(); PrefUtils.setLiveDrawerShown(this); } } private void recomputePhotoAndScrollingMetrics() { mHeaderHeightPixels = mHeaderBox.getHeight(); mPhotoHeightPixels = 0; if (mHasPhoto) { mPhotoHeightPixels = (int) (mGameImageView.getWidth() / PHOTO_ASPECT_RATIO); mPhotoHeightPixels = Math.min(mPhotoHeightPixels, mScrollView.getHeight() * 2 / 3); } ViewGroup.LayoutParams lp; lp = mGameViewContainer.getLayoutParams(); if (lp.height != mPhotoHeightPixels) { lp.height = mPhotoHeightPixels; mGameViewContainer.setLayoutParams(lp); } ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) mDetailsContainer.getLayoutParams(); if (mlp.topMargin != mHeaderHeightPixels + mPhotoHeightPixels) { mlp.topMargin = mHeaderHeightPixels + mPhotoHeightPixels; mDetailsContainer.setLayoutParams(mlp); } onScrollChanged(0, 0); // trigger scroll handling } @Override protected void onDestroy() { super.onDestroy(); if (mGameUpdaterRunnable != null) { mHandler.removeCallbacks(mGameUpdaterRunnable); } if (mGameResultRunnable != null) { mHandler.removeCallbacks(mGameResultRunnable); } if (mScrollView == null) { return; } ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { vto.removeOnGlobalLayoutListener(mGlobalLayoutListener); } } @Override public void onResume() { super.onResume(); LoaderManager manager = getLoaderManager(); manager.initLoader(0, null, this); manager.initLoader(1, null, this); } @Override public void onPause() { super.onPause(); if (mGameUpdaterRunnable != null) { mHandler.removeCallbacks(mGameUpdaterRunnable); } } @Override public void onScrollChanged(int deltaX, int deltaY) { int scrollY = mScrollView.getScrollY(); float newTop = Math.max(mPhotoHeightPixels, scrollY); mHeaderBox.setTranslationY(newTop); float gapFillProgress = 1; if (mPhotoHeightPixels != 0) { gapFillProgress = Math.min(Math.max(UIUtils.getProgress(scrollY, 0, mPhotoHeightPixels), 0), 1); } ViewCompat.setElevation(mHeaderBox, gapFillProgress * mMaxHeaderElevation); // Move background photo (parallax effect) mGameViewContainer.setTranslationY(scrollY * 0.5f); } private void initMapView() { DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int minHeight = displayMetrics.widthPixels > displayMetrics.heightPixels ? displayMetrics.heightPixels - UIUtils.calculateActionBarSize(this) : displayMetrics.widthPixels; mapX = mapY = minHeight - getResources().getDimensionPixelSize(R.dimen.keyline_1) * 2; Picasso.with(this).load(R.drawable.dota2_map).config(Bitmap.Config.RGB_565).resize(mapX, mapY).into(mapView, mLoadMapCallback); for (int i = 0; i < DotaMapModel.DIRE_TOWERS.length; i++) { int x = DotaMapModel.scaleToNewMapSize(DotaMapModel.DIRE_TOWERS_X[i], mapX); int y = DotaMapModel.scaleToNewMapSize(DotaMapModel.DIRE_TOWERS_Y[i], mapY); View v = findViewById(DotaMapModel.DIRE_TOWERS[i]); v.setX(x); v.setY(y); v.setBackground(getResources().getDrawable(R.drawable.dire_tower)); } for (int i = 0; i < DotaMapModel.DIRE_STRUCTURES.length; i++) { int x = DotaMapModel.scaleToNewMapSize(DotaMapModel.DIRE_STRUCTURES_X[i], mapX); int y = DotaMapModel.scaleToNewMapSize(DotaMapModel.DIRE_STRUCTURES_Y[i], mapY); View v = findViewById(DotaMapModel.DIRE_STRUCTURES[i]); v.setX(x); v.setY(y); v.setBackground(getResources().getDrawable(R.drawable.dire_hero_oval)); } for (int i = 0; i < DotaMapModel.RADIANT_TOWERS.length; i++) { int x = DotaMapModel.scaleToNewMapSize(DotaMapModel.RADIANT_TOWERS_X[i], mapX); int y = DotaMapModel.scaleToNewMapSize(DotaMapModel.RADIANT_TOWERS_Y[i], mapY); View v = findViewById(DotaMapModel.RADIANT_TOWERS[i]); v.setX(x); v.setY(y); v.setBackground(getResources().getDrawable(R.drawable.radiant_tower)); } for (int i = 0; i < DotaMapModel.RADIANT_STRUCTURES.length; i++) { int x = DotaMapModel.scaleToNewMapSize(DotaMapModel.RADIANT_STRUCTURES_X[i], mapX); int y = DotaMapModel.scaleToNewMapSize(DotaMapModel.RADIANT_STRUCTURES_Y[i], mapY); View v = findViewById(DotaMapModel.RADIANT_STRUCTURES[i]); v.setX(x); v.setY(y); v.setBackground(getResources().getDrawable(R.drawable.radiant_hero_oval)); } final int iconSizeRoshan = Math.round( TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics())); ImageView v = (ImageView) findViewById(DotaMapModel.ROSHAN); v.setX(DotaMapModel.scaleToNewMapSize(DotaMapModel.ROSHAN_X, mapX)); v.setY(DotaMapModel.scaleToNewMapSize(DotaMapModel.ROSHAN_Y, mapY)); Picasso.with(this).load(R.drawable.drawable_map_roshan).resize(iconSizeRoshan, iconSizeRoshan).centerCrop() .into(v); } @Override public void onBackPressed() { super.onBackPressed(); getLoaderManager().destroyLoader(0); getLoaderManager().destroyLoader(1); finish(); overridePendingTransition(R.anim.anim_scale_in, R.anim.anim_slide_out); } public void setViewVisible(int viewId, boolean isVisible) { findViewById(viewId).setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mLiveTickerAdapter.onSaveInstanceState(outState); outState.putBoolean(KEY_GAME_COMPLETE, gameCompleted); } @Override public void initPlayerMapIcon(int playerSlot, int resId) { int iconSize = Math.round( TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics())); int paddingSize = Math.round( TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics())); (findViewById(mPlayerMiniMapIcons[playerSlot])).setPadding(paddingSize, paddingSize, paddingSize, paddingSize); Picasso.with(this).load(resId).resize(iconSize, iconSize).centerCrop() .into((ImageView) findViewById(mPlayerMiniMapIcons[playerSlot])); } @Override public void updatePlayerMapIcon(int playerSlot, float x, float y, int resId) { int scaledX = DotaMapModel.scaleXFromAPI(x, mapX); int scaledY = DotaMapModel.scaleYFromAPI(y, mapY); (findViewById(mPlayerMiniMapIcons[playerSlot])).animate().setDuration(250).translationX(scaledX) .translationY(scaledY).setInterpolator(new AccelerateDecelerateInterpolator()).start(); findViewById(mPlayerMiniMapIcons[playerSlot]).setBackground(getResources().getDrawable(resId)); } @Override public void initPlayerView(int playerSlot, String name) { final ViewGroup teamBlock = (playerSlot < 5 ? (ViewGroup) findViewById(R.id.game_radiant_block) : (ViewGroup) findViewById(R.id.game_dire_block)); mPlayerLayouts[playerSlot] = getLayoutInflater().inflate(R.layout.player_live_detail, teamBlock, false); ((TextView) mPlayerLayouts[playerSlot].findViewById(R.id.player_name)).setText(name); teamBlock.addView(mPlayerLayouts[playerSlot]); } @Override public void setPlayerKDA(int playerSlot, int k, int d, int a) { ((TextView) mPlayerLayouts[playerSlot].findViewById(R.id.player_kda)).setText(k + " / " + d + " / " + a); } @Override public void setPlayerCS(int playerSlot, int lh, int d) { ((TextView) mPlayerLayouts[playerSlot].findViewById(R.id.player_lhd)).setText(lh + " / " + d); } @Override public void setPlayerPortrait(int playerSlot, int resId) { Picasso.with(this).load(resId) .into((ImageView) mPlayerLayouts[playerSlot].findViewById(R.id.player_hero_logo)); } @Override public void setPlayerItem(int playerSlot, int itemSlot, int resId) { ImageView imageView = (ImageView) mPlayerLayouts[playerSlot].findViewById(itemViewIds[itemSlot]); switch (resId) { case 0: imageView.animate().setDuration(200).alpha(0).setInterpolator(new AccelerateInterpolator()); default: imageView.setImageResource(resId); imageView.animate().setDuration(200).alpha(1).setInterpolator(new DecelerateInterpolator()); } } @Override public void setPlayerDesc(int playerSlot, int playerLevel, String heroName) { ((TextView) mPlayerLayouts[playerSlot].findViewById(R.id.player_hero_detail)) .setText("Lv " + playerLevel + " " + heroName); } @Override public void setGameTime(String gameTime) { mGameTimeView.setText(gameTime); } @Override public void setGameScores(int radiantScore, int direScore) { mGameScoreViewLeft.setText(Integer.toString(radiantScore)); mGameScoreViewRight.setText(Integer.toString(direScore)); } @Override public void setTeamHeaders(String radiantName, String direName) { ((TextView) findViewById(R.id.game_radiant_header)).setText(radiantName); ((TextView) findViewById(R.id.game_dire_header)).setText(direName); ImageView mGameTeamLeftImageView = (ImageView) findViewById(R.id.game_team_left_photo); ImageView mGameTeamRightImageView = (ImageView) findViewById(R.id.game_team_right_photo); String intentLeftName = getIntent().getStringExtra(ARGS_GAME_TNL); if (StringUtils.compareTokensToTokens(intentLeftName, direName)) { Picasso.with(this).load(getIntent().getStringExtra(ARGS_GAME_TLL)).fit().into(mGameTeamRightImageView); Picasso.with(this).load(getIntent().getStringExtra(ARGS_GAME_TLR)).fit().into(mGameTeamLeftImageView); } else { Picasso.with(this).load(getIntent().getStringExtra(ARGS_GAME_TLL)).fit().into(mGameTeamLeftImageView); Picasso.with(this).load(getIntent().getStringExtra(ARGS_GAME_TLR)).fit().into(mGameTeamRightImageView); } } @Override public void addTick(int resId, String name, String details, int team) { if (gameCompleted) { return; } TickerEvent tickerEvent = new TickerEvent(); tickerEvent.resId = resId; tickerEvent.pName = name; tickerEvent.pEvents = details; tickerEvent.team = team; mLiveTickerAdapter.insert(tickerEvent, 0); } @Override public void addTickSeparator(String time) { TickerSeparator tickerSeparator = new TickerSeparator(); tickerSeparator.time = time; mLiveTickerAdapter.insert(tickerSeparator, 0); } @Override public Loader<DotaGameData> onCreateLoader(int id, Bundle args) { if (id == 0) { return new SAPISingleLiveGameLoader(this, matchId); } else { return new SAPIGameResultLoader(this, matchId); } } @Override public void onLoadFinished(Loader<DotaGameData> loader, DotaGameData data) { if (data == null && loader.getId() == 0) { // if data is null, then we didn't receive anything from valve servers. // there's a chance that the game is done, so... mHandler.post(mGameResultRunnable); } else if (data == null && loader.getId() == 1) { // there's a chance that the steam api is down, should "wait", but attempt to fetch // new data. mHandler.postDelayed(mGameUpdaterRunnable, GAME_UPDATE_INTERVAL); mUpdateProgressBar.setProgress(2000); mUpdateProgressBar.startAnimation(mUpdateProgressAnim); } else if (data.getType() == 50) { // this only comes from gameresulttaskloader indicating that there's no result: // only means that the game may still be live, so we'll try to grab new data. mHandler.postDelayed(mGameUpdaterRunnable, GAME_UPDATE_INTERVAL); mUpdateProgressBar.setProgress(2000); mUpdateProgressBar.startAnimation(mUpdateProgressAnim); } else if (data.getType() == 0) { // this only comes from gamedetailtaskloader indicating that there's data. // we should process it, then schedule an update to fetch new data. // we should keep the logic for doing live/result data here only. mPresenter.processNewData((Game) data); mHandler.postDelayed(mGameUpdaterRunnable, GAME_UPDATE_INTERVAL); LogUtils.LOGD(TAG, "gotMatchData"); mUpdateProgressBar.setProgress(2000); mUpdateProgressBar.startAnimation(mUpdateProgressAnim); } else if (data.getType() == 1) { // this only comes from gameresulttaskloader indicating result was found: // we should process it, then not bother scheduling any further updates. LogUtils.LOGD(TAG, "gotResultData"); mPresenter.processResultData((FinishedGame) data); gameCompleted = true; } } @Override public void onLoaderReset(Loader<DotaGameData> loader) { } class ProgressBarAnimation extends Animation { private ProgressBar progressBar; private float mStart; private float mEnd; public ProgressBarAnimation(ProgressBar progressBar, float start, float end) { super(); this.progressBar = progressBar; mStart = start; mEnd = end; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); float value = mStart + (mEnd - mStart) * interpolatedTime; progressBar.setProgress((int) value); } } }