Guest User

Untitled

a guest
May 4th, 2020
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.12 KB | None | 0 0
  1. /*
  2. * Copyright 2016 Google Inc. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. *    http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package io.mattcarroll.hover;
  17.  
  18. import android.annotation.SuppressLint;
  19. import android.graphics.Point;
  20. import android.support.annotation.NonNull;
  21. import android.support.annotation.Nullable;
  22. import android.support.v7.util.ListUpdateCallback;
  23. import android.util.Log;
  24. import android.view.View;
  25.  
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31.  
  32. /**
  33. * {@link HoverViewState} that operates the {@link HoverView} when it is expanded. Expanded means
  34. * that all menu tabs are displayed along the top of the {@code HoverView} and the selected
  35. * {@link HoverMenu.Section}'s {@link Content} is displayed below the row of tabs.
  36. *
  37. * When the selected tab is tapped again by the user, the {@code HoverView} is transitioned to its
  38. * collapsed state.
  39. */
  40. class HoverViewStateExpanded extends BaseHoverViewState {
  41.  
  42. private static final String TAG = "HoverMenuViewStateExpanded";
  43. private static final int ANCHOR_TAB_X_OFFSET_IN_PX = 100;
  44. private static final int ANCHOR_TAB_Y_OFFSET_IN_PX = 100;
  45. private static final int TAB_SPACING_IN_PX = 200;
  46. private static final int TAB_APPEARANCE_DELAY_IN_MS = 100;
  47.  
  48. private boolean mHasControl = false;
  49. private HoverView mHoverView;
  50. private boolean mHasMenu = false;
  51. private FloatingTab mSelectedTab;
  52. private final List<FloatingTab> mChainedTabs = new ArrayList<>();
  53. private final List<TabChain> mTabChains = new ArrayList<>();
  54. private final Map<FloatingTab, HoverMenu.Section> mSections = new HashMap<>();
  55. private Point mDock;
  56. private Listener mListener;
  57.  
  58. private final Runnable mShowTabsRunnable = new Runnable() {
  59. @Override
  60. public void run() {
  61. mHoverView.mScreen.getShadeView().show();
  62. mHoverView.mScreen.getContentDisplay().selectedTabIs(mSelectedTab);
  63.  
  64. HoverMenu.Section selectedSection = null != mHoverView.mSelectedSectionId
  65. ? mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId)
  66. : mHoverView.mMenu.getSection(0);
  67. mHoverView.mScreen.getContentDisplay().displayContent(selectedSection.getContent());
  68.  
  69. mHoverView.mScreen.getContentDisplay().setVisibility(View.VISIBLE);
  70.  
  71. mHoverView.notifyListenersExpanded();
  72. if (null != mListener) {
  73. mListener.onExpanded();
  74. }
  75. }
  76. };
  77.  
  78. HoverViewStateExpanded() { }
  79.  
  80. @Override
  81. public void takeControl(@NonNull HoverView hoverView) {
  82. Log.d(TAG, "Taking control.");
  83. super.takeControl(hoverView);
  84. if (mHasControl) {
  85. throw new RuntimeException("Cannot take control of a FloatingTab when we already control one.");
  86. }
  87.  
  88. mHasControl = true;
  89. mHoverView = hoverView;
  90. mHoverView.mState = this;
  91. mHoverView.makeTouchableInWindow();
  92. mHoverView.requestFocus(); // For handling hardware back button presses.
  93. mDock = new Point(
  94. mHoverView.mScreen.getWidth() - ANCHOR_TAB_X_OFFSET_IN_PX,
  95. ANCHOR_TAB_Y_OFFSET_IN_PX
  96. );
  97. if (null != mHoverView.mMenu) {
  98. Log.d(TAG, "Already has menu. Expanding.");
  99. setMenu(mHoverView.mMenu);
  100. }
  101.  
  102. mHoverView.makeTouchableInWindow();
  103. }
  104.  
  105. private void expandMenu() {
  106. // If the selected tab is not already visible then we want to dock it immediately without
  107. // animation.
  108. boolean dockSelectedTabImmediately = null == mHoverView.mScreen.getChainedTab(mHoverView.mSelectedSectionId);
  109.  
  110. createChainedTabs();
  111. chainTabs(!dockSelectedTabImmediately);
  112.  
  113. if (dockSelectedTabImmediately) {
  114. mSelectedTab.dockImmediately();
  115. mHoverView.post(mShowTabsRunnable);
  116. } else {
  117. mSelectedTab.dock(mShowTabsRunnable);
  118. }
  119.  
  120. mHoverView.notifyListenersExpanding();
  121. if (null != mListener) {
  122. mListener.onExpanding();
  123. }
  124. }
  125.  
  126. private void createChainedTabs() {
  127. Log.d(TAG, "Creating chained tabs");
  128. if (null != mHoverView.mMenu) {
  129. for (int i = 0; i < mHoverView.mMenu.getSectionCount(); ++i) {
  130. HoverMenu.Section section = mHoverView.mMenu.getSection(i);
  131. Log.d(TAG, "Creating tab view for: " + section.getId());
  132. final FloatingTab chainedTab = mHoverView.mScreen.createChainedTab(
  133. section.getId(),
  134. section.getTabView()
  135. );
  136. Log.d(TAG, "Created FloatingTab for ID " + section.getId());
  137.  
  138. if (!mHoverView.mSelectedSectionId.equals(section.getId())) {
  139. chainedTab.disappearImmediate();
  140. } else {
  141. mSelectedTab = chainedTab;
  142. }
  143.  
  144. Log.d(TAG, "Adding tabView: " + section.getTabView() + ". Its parent is: " + section.getTabView().getParent());
  145. mChainedTabs.add(chainedTab);
  146. mSections.put(chainedTab, section);
  147. mTabChains.add(new TabChain(chainedTab, TAB_SPACING_IN_PX));
  148.  
  149. chainedTab.setOnClickListener(new View.OnClickListener() {
  150. @Override
  151. public void onClick(View v) {
  152. // TODO: is not stable and is crashing.
  153. onTabSelected(chainedTab);
  154. }
  155. });
  156. }
  157. }
  158. }
  159.  
  160. private void chainTabs(boolean animateSelectedTab) {
  161. Log.d(TAG, "Chaining tabs.");
  162. FloatingTab predecessorTab = mChainedTabs.get(0);
  163.  
  164. // Find the selected tab.
  165. int selectedTabIndex = 0;
  166. for (int i = 0; i < mChainedTabs.size(); ++i) {
  167. if (mSelectedTab == mChainedTabs.get(i)) {
  168. selectedTabIndex = i;
  169. break;
  170. }
  171. }
  172.  
  173. for (int i = 0; i < mChainedTabs.size(); ++i) {
  174. final FloatingTab chainedTab = mChainedTabs.get(i);
  175. final TabChain tabChain = mTabChains.get(i);
  176.  
  177. if (i == 0) {
  178. // TODO: generalize the notion of a predecessor so that the 1st tab doesn't need
  179. // TODO: to be treated in a special way.
  180. tabChain.chainTo(mDock);
  181. tabChain.tightenChain(!animateSelectedTab);
  182. } else {
  183. final FloatingTab currentPredecessor = predecessorTab;
  184. int displayDelayInMillis = (int) (Math.abs(selectedTabIndex - i) * 100);
  185. tabChain.chainTo(currentPredecessor);
  186. chainedTab.postDelayed(new Runnable() {
  187. @Override
  188. public void run() {
  189. Log.d(TAG, "Chaining " + chainedTab.getTabId() + " to " + currentPredecessor.getTabId());
  190. tabChain.tightenChain();
  191. }
  192. }, displayDelayInMillis);
  193. }
  194.  
  195. predecessorTab = chainedTab;
  196. }
  197. }
  198.  
  199. @Override
  200. public void expand() {
  201. Log.d(TAG, "Instructed to expand, but already expanded.");
  202. }
  203.  
  204. @Override
  205. public void collapse() {
  206. Log.d(TAG, "Collapsing.");
  207. changeState(mHoverView.mCollapsed);
  208. }
  209.  
  210. @Override
  211. public void close() {
  212. Log.d(TAG, "Closing.");
  213. changeState(mHoverView.mClosed);
  214. }
  215.  
  216. private void changeState(@NonNull final HoverViewState nextState) {
  217. Log.d(TAG, "Giving up control.");
  218. if (!mHasControl) {
  219. throw new RuntimeException("Cannot give control to another HoverMenuController when we don't have the HoverTab.");
  220. }
  221.  
  222. if (null != mHoverView.mMenu) {
  223. mHoverView.mMenu.setUpdatedCallback(null);
  224. }
  225.  
  226. mHasControl = false;
  227. mHasMenu = false;
  228. mHoverView.mScreen.getContentDisplay().selectedTabIs(null);
  229. mHoverView.mScreen.getContentDisplay().displayContent(null);
  230. mHoverView.mScreen.getContentDisplay().setVisibility(View.GONE);
  231. mHoverView.mScreen.getShadeView().hide();
  232. mHoverView.setState(nextState);
  233. unchainTabs(new Runnable() {
  234. @Override
  235. public void run() {
  236. Log.d(TAG, "Running unchained runnable.");
  237. // We wait to nullify our HoverMenuView because some final animations need it.
  238. // TODO: maybe the answer is for the collapse state to handle what happens to the tabs and content display and shade?
  239. mHoverView = null;
  240. }
  241. });
  242. }
  243.  
  244. private int mTabsToUnchainCount;
  245. private void unchainTabs(@Nullable final Runnable onUnChained) {
  246. int selectedTabIndex = 0;
  247. for (int i = 0; i < mChainedTabs.size(); ++i) {
  248. if (mSelectedTab == mChainedTabs.get(i)) {
  249. selectedTabIndex = i;
  250. break;
  251. }
  252. }
  253.  
  254. int unchainCompletionTime = 0;
  255. mTabsToUnchainCount = mChainedTabs.size() - 1; // -1 for selected tab
  256. for (int i = 0; i < mChainedTabs.size(); ++i) {
  257. final FloatingTab chainedTab = mChainedTabs.get(i);
  258. final TabChain tabChain = mTabChains.get(i);
  259.  
  260. if (mSelectedTab != chainedTab) {
  261. int displayDelayInMillis = Math.abs(selectedTabIndex - i) * TAB_APPEARANCE_DELAY_IN_MS;
  262. unchainCompletionTime = Math.max(unchainCompletionTime, displayDelayInMillis);
  263. Log.d(TAG, "Queue'ing chained tab disappearance with delay: " + displayDelayInMillis);
  264. chainedTab.postDelayed(new Runnable() {
  265. @Override
  266. public void run() {
  267. tabChain.unchain(new Runnable() {
  268. @Override
  269. public void run() {
  270. Log.d(TAG, "Destroying chained tab: " + chainedTab);
  271. mHoverView.mScreen.destroyChainedTab(chainedTab);
  272.  
  273. --mTabsToUnchainCount;
  274. if (0 == mTabsToUnchainCount && null != onUnChained) {
  275. onUnChained.run();
  276. }
  277. }
  278. });
  279. }
  280. }, displayDelayInMillis);
  281. }
  282. }
  283.  
  284. mChainedTabs.clear();
  285. mTabChains.clear();
  286.  
  287. // If there was only 1 tab, run onUnChained callback now.
  288. if (0 == mTabsToUnchainCount && null != onUnChained) {
  289. onUnChained.run();
  290. }
  291. }
  292.  
  293. @Override
  294. public void setMenu(@Nullable HoverMenu menu) {
  295. Log.d(TAG, "Setting menu.");
  296. mHoverView.mMenu = menu;
  297.  
  298. // Expanded menus can't be null/empty. If it is then go to closed state.
  299. if (null == mHoverView.mMenu || mHoverView.mMenu.getSectionCount() == 0) {
  300. close();
  301. return;
  302. }
  303.  
  304. mHoverView.restoreVisualState();
  305.  
  306. if (null == mHoverView.mSelectedSectionId || null == mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId)) {
  307. mHoverView.mSelectedSectionId = mHoverView.mMenu.getSection(0).getId();
  308. }
  309.  
  310. mHoverView.mMenu.setUpdatedCallback(new ListUpdateCallback() {
  311. @Override
  312. public void onInserted(int position, int count) {
  313. Log.d(TAG, "onInserted. Position: " + position + ", Count: " + count);
  314. int[] sectionIndices = new int[count];
  315. for (int i = position; i < position + count; ++i) {
  316. sectionIndices[i - position] = i;
  317. }
  318. createTabsForIndices(sectionIndices);
  319. }
  320.  
  321. @Override
  322. public void onRemoved(int position, int count) {
  323. Log.d(TAG, "onRemoved. Position: " + position + ", Count: " + count);
  324. int[] sectionIndices = new int[count];
  325. for (int i = position; i < position + count; ++i) {
  326. sectionIndices[i - position] = i;
  327. }
  328. removeSections(sectionIndices);
  329. }
  330.  
  331. @Override
  332. public void onMoved(int fromPosition, int toPosition) {
  333. Log.d(TAG, "onMoved from: " + fromPosition + ", to: " + toPosition);
  334. reorderSection(fromPosition, toPosition);
  335. }
  336.  
  337. @Override
  338. public void onChanged(int position, int count, Object payload) {
  339. Log.d(TAG, "Tab(s) changed. From: " + position + ", To: " + count);
  340. int[] sectionIndices = new int[count];
  341. for (int i = position; i < position + count; ++i) {
  342. sectionIndices[i - position] = i;
  343. }
  344. updateSections(sectionIndices);
  345. }
  346. });
  347.  
  348. if (mHasControl && !mHasMenu) {
  349. Log.d(TAG, "Has control. Received initial menu. Expanding menu.");
  350. expandMenu();
  351. } else if (mHasControl) {
  352. Log.d(TAG, "Has control. Already had menu. Switching menu.");
  353. transitionDisplayFromOldMenuToNew();
  354. }
  355. mHasMenu = true;
  356. }
  357.  
  358. private void transitionDisplayFromOldMenuToNew() {
  359. // TODO: implement a generalized display update mechanism rather than have sprawling update
  360. // TODO: logic throughout this Class.
  361. for (int i = 0; i < mHoverView.mMenu.getSectionCount(); ++i) {
  362. if (i < mChainedTabs.size()) {
  363. updateSection(i);
  364. } else {
  365. createTabsForIndices(i);
  366. }
  367. }
  368.  
  369. if (mChainedTabs.size() > mHoverView.mMenu.getSectionCount()) {
  370. int[] removedSections = new int[mChainedTabs.size() - mHoverView.mMenu.getSectionCount()];
  371. for (int i = mHoverView.mMenu.getSectionCount(); i < mChainedTabs.size(); ++i) {
  372. removedSections[i - mHoverView.mMenu.getSectionCount()] = i;
  373. }
  374. removeSections(removedSections);
  375. }
  376. }
  377.  
  378. @Override
  379. public boolean respondsToBackButton() {
  380. return true;
  381. }
  382.  
  383. @Override
  384. public void onBackPressed() {
  385. collapse();
  386. }
  387.  
  388. @SuppressLint("LongLogTag")
  389. private void createTabsForIndices(int... sectionIndices) {
  390. for (int sectionIndex : sectionIndices) {
  391. Log.d(TAG, "Creating tab for section at index " + sectionIndex);
  392. HoverMenu.Section section = mHoverView.mMenu.getSection(sectionIndex);
  393. Log.d(TAG, "Adding new tab. Section: " + sectionIndex + ", ID: " + section.getId());
  394. FloatingTab newTab = addTab(section.getId(), section.getTabView(), sectionIndex);
  395. mSections.put(newTab, section);
  396. }
  397.  
  398. updateChainedPositions();
  399. }
  400.  
  401. private FloatingTab addTab(@NonNull HoverMenu.SectionId sectionId,
  402. @NonNull View tabView,
  403. int position) {
  404. final FloatingTab newTab = mHoverView.mScreen.createChainedTab(
  405. sectionId,
  406. tabView
  407. );
  408. newTab.disappearImmediate();
  409. if (mChainedTabs.size() <= position) {
  410. // This section was appended to the end.
  411. mChainedTabs.add(newTab);
  412. mTabChains.add(new TabChain(newTab, TAB_SPACING_IN_PX));
  413. } else {
  414. mChainedTabs.add(position, newTab);
  415. mTabChains.add(position, new TabChain(newTab, TAB_SPACING_IN_PX));
  416. }
  417.  
  418. newTab.setOnClickListener(new View.OnClickListener() {
  419. @Override
  420. public void onClick(View v) {
  421. onTabSelected(newTab);
  422. }
  423. });
  424.  
  425. return newTab;
  426. }
  427.  
  428. private void reorderSection(int fromPosition, int toPosition) {
  429. Log.d(TAG, "Tab moved. From: " + fromPosition + ", To: " + toPosition);
  430. FloatingTab chainedTab = mChainedTabs.remove(fromPosition);
  431. mChainedTabs.add(toPosition, chainedTab);
  432. TabChain tabChain = mTabChains.remove(fromPosition);
  433. mTabChains.add(toPosition, tabChain);
  434.  
  435. updateChainedPositions();
  436. }
  437.  
  438. private void updateSections(int... sectionIndices) {
  439. Log.d(TAG, "Tab(s) changed: " + Arrays.toString(sectionIndices));
  440. for (int sectionIndex : sectionIndices) {
  441. updateSection(sectionIndex);
  442. }
  443. }
  444.  
  445. private void updateSection(int sectionIndex) {
  446. HoverMenu.Section section = mHoverView.mMenu.getSection(sectionIndex);
  447. if (null == section) {
  448. Log.e(TAG, "Tried to update section " + sectionIndex + " but could not locate the corresponding Section.");
  449. return;
  450. }
  451.  
  452. // Update Tab View
  453. FloatingTab chainedTab = mChainedTabs.get(sectionIndex);
  454. chainedTab.setTabView(section.getTabView());
  455.  
  456. // Update Section Content if this Section is currently selected.
  457. if (mHoverView.mSelectedSectionId.equals(mHoverView.mMenu.getSection(sectionIndex).getId())) {
  458. mHoverView.mScreen.getContentDisplay().displayContent(section.getContent());
  459. }
  460. }
  461.  
  462. private void removeSections(int... sectionIndices) {
  463. Log.d(TAG, "Tab(s) removed: " + Arrays.toString(sectionIndices));
  464. // Sort the indices so that they appear from lowest to highest. Then process
  465. // in reverse order so that we don't remove sections out from under us.
  466. Arrays.sort(sectionIndices);
  467. for (int i = sectionIndices.length - 1; i >= 0; --i) {
  468. removeSection(sectionIndices[i]);
  469. }
  470.  
  471. updateChainedPositions();
  472. }
  473.  
  474. private void removeSection(int sectionIndex) {
  475. final FloatingTab chainedTab = mChainedTabs.remove(sectionIndex);
  476. TabChain tabChain = mTabChains.remove(sectionIndex);
  477. tabChain.unchain(new Runnable() {
  478. @Override
  479. public void run() {
  480. mHoverView.mScreen.destroyChainedTab(chainedTab);
  481. }
  482. });
  483.  
  484. // If the removed section was the selected section then select a new section.
  485. HoverMenu.Section removedSection = mSections.get(chainedTab);
  486. if (removedSection.getId().equals(mHoverView.mSelectedSectionId)) {
  487. int newSelectionIndex = 0;
  488. if (sectionIndex - 1 < mHoverView.mMenu.getSectionCount() - 1) {
  489. newSelectionIndex = sectionIndex - 1;
  490. } else {
  491. newSelectionIndex = mHoverView.mMenu.getSectionCount() - 1;
  492. }
  493.  
  494. selectSection(mHoverView.mMenu.getSection(newSelectionIndex));
  495. }
  496.  
  497. // TODO: This cleanup should be centralized.
  498. chainedTab.setOnClickListener(null);
  499. mSections.remove(chainedTab);
  500. }
  501.  
  502. private void updateChainedPositions() {
  503. TabChain firstChain = mTabChains.get(0);
  504. firstChain.chainTo(mDock);
  505. firstChain.tightenChain();
  506.  
  507. FloatingTab predecessor = mChainedTabs.get(0);
  508. for (int i = 1; i < mChainedTabs.size(); ++i) {
  509. FloatingTab chainedTab = mChainedTabs.get(i);
  510. TabChain tabChain = mTabChains.get(i);
  511. tabChain.chainTo(predecessor);
  512. tabChain.tightenChain();
  513. predecessor = chainedTab;
  514. }
  515. }
  516.  
  517. private void onTabSelected(@NonNull FloatingTab selectedTab) {
  518. Log.d(TAG, "onTabSelected(). Selected section: " + mSections.get(selectedTab).getId()
  519. + ", mSelectedSectionId: " + mHoverView.mSelectedSectionId);
  520. HoverMenu.Section section = mSections.get(selectedTab);
  521. if (!section.getId().equals(mHoverView.mSelectedSectionId)) {
  522. selectSection(section);
  523. } else {
  524. collapse();
  525. }
  526. }
  527.  
  528. private void selectSection(@NonNull HoverMenu.Section section) {
  529. mHoverView.mSelectedSectionId = section.getId();
  530. mSelectedTab = mHoverView.mScreen.getChainedTab(mHoverView.mSelectedSectionId);
  531. ContentDisplay contentDisplay = mHoverView.mScreen.getContentDisplay();
  532. contentDisplay.selectedTabIs(mSelectedTab);
  533. contentDisplay.displayContent(section.getContent());
  534. }
  535.  
  536. // TODO: do we need this?
  537. public void setListener(@NonNull Listener listener) {
  538. mListener = listener;
  539. }
  540.  
  541. public interface Listener {
  542. void onExpanding();
  543.  
  544. void onExpanded();
  545.  
  546. // TODO: do we need this?
  547. void onCollapseRequested();
  548. }
  549. }
Add Comment
Please, Sign In to add comment