PageRenderTime 48ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/src/br/com/carlosrafaelgn/fplay/ui/BgListView.java

https://gitlab.com/madamovic-bg/FPlayAndroid
Java | 968 lines | 821 code | 58 blank | 89 comment | 260 complexity | 8f422b9a4dac431565318be77765b75e MD5 | raw file
  1. //
  2. // FPlayAndroid is distributed under the FreeBSD License
  3. //
  4. // Copyright (c) 2013-2014, Carlos Rafael Gimenes das Neves
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // 1. Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // 2. Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  20. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. //
  27. // The views and conclusions contained in the software and documentation are those
  28. // of the authors and should not be interpreted as representing official policies,
  29. // either expressed or implied, of the FreeBSD Project.
  30. //
  31. // https://github.com/carlosrafaelgn/FPlayAndroid
  32. //
  33. package br.com.carlosrafaelgn.fplay.ui;
  34. import android.annotation.TargetApi;
  35. import android.content.Context;
  36. import android.graphics.Canvas;
  37. import android.graphics.Paint;
  38. import android.graphics.Rect;
  39. import android.graphics.drawable.Drawable;
  40. import android.os.Build;
  41. import android.os.SystemClock;
  42. import android.support.annotation.NonNull;
  43. import android.text.Layout.Alignment;
  44. import android.text.StaticLayout;
  45. import android.util.AttributeSet;
  46. import android.view.KeyEvent;
  47. import android.view.MotionEvent;
  48. import android.view.SoundEffectConstants;
  49. import android.view.View;
  50. import android.view.ViewDebug.ExportedProperty;
  51. import android.view.accessibility.AccessibilityNodeInfo;
  52. import android.widget.AbsListView;
  53. import android.widget.ListAdapter;
  54. import android.widget.ListView;
  55. import br.com.carlosrafaelgn.fplay.list.BaseItem;
  56. import br.com.carlosrafaelgn.fplay.list.BaseList;
  57. import br.com.carlosrafaelgn.fplay.ui.drawable.BorderDrawable;
  58. import br.com.carlosrafaelgn.fplay.ui.drawable.ColorDrawable;
  59. import br.com.carlosrafaelgn.fplay.ui.drawable.NullDrawable;
  60. public final class BgListView extends ListView implements ListView.OnScrollListener {
  61. public interface OnAttachedObserver {
  62. void onBgListViewAttached(BgListView list);
  63. }
  64. public interface OnBgListViewKeyDownObserver {
  65. boolean onBgListViewKeyDown(BgListView list, int keyCode);
  66. }
  67. public static final int SCROLLBAR_SYSTEM = 0;
  68. public static final int SCROLLBAR_LARGE = 1;
  69. public static final int SCROLLBAR_INDEXED = 2;
  70. public static final int SCROLLBAR_NONE = 3;
  71. private OnAttachedObserver attachedObserver;
  72. private OnBgListViewKeyDownObserver keyDownObserver;
  73. private OnClickListener emptyListClickListener;
  74. private StaticLayout emptyLayout;
  75. private BaseList<? extends BaseItem> adapter;
  76. private boolean notified, attached, measured, sized, ignoreTouchMode, ignorePadding, tracking;
  77. private int leftPadding, topPadding, rightPadding, bottomPadding, scrollBarType, scrollBarWidth, scrollBarThumbTop, scrollBarThumbHeight,
  78. scrollBarTop, scrollBarLeft, scrollBarBottom, viewWidth, viewHeight, contentsHeight, itemHeight, itemCount, scrollBarThumbOffset, scrollState;
  79. private String[] sections;
  80. private int[] sectionPositions;
  81. public boolean skipUpDownTranslation;
  82. int extraState;
  83. public BgListView(Context context) {
  84. super(context);
  85. init();
  86. }
  87. public BgListView(Context context, AttributeSet attrs) {
  88. super(context, attrs);
  89. init();
  90. }
  91. public BgListView(Context context, AttributeSet attrs, int defStyle) {
  92. super(context, attrs, defStyle);
  93. init();
  94. }
  95. @SuppressWarnings("deprecation")
  96. private void init() {
  97. super.setSelector(new NullDrawable());
  98. super.setDivider(null);
  99. super.setDividerHeight(0);
  100. super.setCacheColorHint(UI.color_list);
  101. super.setHorizontalFadingEdgeEnabled(false);
  102. super.setVerticalFadingEdgeEnabled(false);
  103. //super.setFadingEdgeLength(0);
  104. super.setFocusableInTouchMode(!UI.hasTouch);
  105. super.setFocusable(true);
  106. super.setScrollingCacheEnabled(false);
  107. super.setDrawingCacheEnabled(false);
  108. super.setChildrenDrawingCacheEnabled(false);
  109. super.setAnimationCacheEnabled(false);
  110. ignorePadding = true;
  111. super.setHorizontalScrollBarEnabled(false);
  112. super.setVerticalScrollBarEnabled(true);
  113. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
  114. setVerticalScrollBarPosition();
  115. ignorePadding = false;
  116. super.setBackgroundDrawable(new ColorDrawable(UI.color_list));
  117. super.setOverscrollHeader(null); //Motorola bug!!! :P
  118. super.setOverscrollFooter(null); //Motorola bug!!! :P
  119. setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
  120. UI.prepareEdgeEffect(this, false);
  121. //List color turns black while Scrolling
  122. //http://stackoverflow.com/questions/8531006/list-color-turns-black-while-scrolling
  123. //Remove shadow from top and bottom of ListView in android?
  124. //http://stackoverflow.com/questions/7106692/remove-shadow-from-top-and-bottom-of-listview-in-android
  125. //Changing the ListView shadow color and size
  126. //http://stackoverflow.com/questions/5627063/changing-the-listview-shadow-color-and-size
  127. }
  128. //massive workaround!!!
  129. //
  130. //according to this:
  131. //http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/widget/AdapterView.java#AdapterView.setFocusable%28boolean%29
  132. //AdapterView overrides setFocusable and setFocusableInTouchMode in a way I don't like...
  133. //it calls View.setFocusable with false if the adapter is empty!!!
  134. //therefore, the only way to make it focusable at any time, is to pretend to be in filtermode at all times!
  135. @Override
  136. protected boolean isInFilterMode() {
  137. return true;
  138. }
  139. @Override
  140. public CharSequence getContentDescription() {
  141. return null;
  142. }
  143. @Override
  144. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  145. public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
  146. super.onInitializeAccessibilityNodeInfo(info);
  147. info.setClassName("br.com.carlosrafaelgn.fplay.activity.ActivityHost");
  148. if (itemCount == 0) {
  149. info.setText(emptyLayout == null ? "" : emptyLayout.getText());
  150. } else {
  151. info.setText("");
  152. }
  153. }
  154. @SuppressWarnings("deprecation")
  155. public void setTopBorder() {
  156. super.setBackgroundDrawable(new BorderDrawable(UI.color_highlight, UI.color_list, 0, UI.thickDividerSize, 0, 0));
  157. setPadding(0, UI.thickDividerSize, 0, 0);
  158. UI.offsetTopEdgeEffect(this);
  159. }
  160. @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  161. @Override
  162. public void setBackground(Drawable background) {
  163. }
  164. @SuppressWarnings("deprecation")
  165. @Override
  166. @Deprecated
  167. public void setBackgroundDrawable(Drawable background) {
  168. }
  169. @Override
  170. public void setBackgroundResource(int resid) {
  171. }
  172. @Override
  173. public void setBackgroundColor(int color) {
  174. }
  175. @Override
  176. @ExportedProperty(category = "drawing")
  177. public boolean isOpaque() {
  178. return true;
  179. }
  180. @Override
  181. protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
  182. //massive workaround!!!
  183. //
  184. //ListView's onFocusChanged has a BUG:
  185. //it scrolls the view vertically when the view has top padding != 0
  186. //
  187. //according to the source files
  188. //http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/widget/AbsListView.java
  189. //http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r1/android/widget/AbsListView.java
  190. //http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/widget/ListView.java
  191. //http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r1/android/widget/ListView.java
  192. //if previouslyFocusedRect is null and the control is in touch mode,
  193. //nothing is done, and therefore, the scroll does not happen ;)
  194. extraState = (gainFocus ? UI.STATE_SELECTED : 0);
  195. ignoreTouchMode = true;
  196. super.onFocusChanged(gainFocus, direction, gainFocus ? null : previouslyFocusedRect);
  197. int s;
  198. if (adapter != null) {
  199. s = adapter.getSelection();
  200. if (gainFocus) {
  201. //do not change to itemCount!
  202. final int ic = adapter.getCount();
  203. if (s < 0 || s >= ic) {
  204. s = getFirstVisiblePosition();
  205. if (s < 0)
  206. s = 0;
  207. if (s < ic) {
  208. adapter.setSelection(s, true);
  209. if (s <= getFirstVisiblePosition() || s >= getLastVisiblePosition())
  210. centerItem(s);
  211. }
  212. }
  213. }
  214. if (s >= 0 && (s >= getFirstVisiblePosition() && s <= getLastVisiblePosition())) {
  215. final int c = s - getFirstVisiblePosition();
  216. if (c >= 0 && c < getChildCount())
  217. getChildAt(c).invalidate();
  218. }
  219. }
  220. ignoreTouchMode = false;
  221. }
  222. @Override
  223. @ExportedProperty
  224. public boolean isInTouchMode() {
  225. return (ignoreTouchMode | super.isInTouchMode());
  226. }
  227. @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  228. private void smoothScroll(int position, int y) {
  229. smoothScrollToPositionFromTop(position, y);
  230. }
  231. public void centerItemSmoothly(int position) {
  232. //do not change to itemCount!
  233. if (position < 0 || adapter == null || position >= adapter.getCount())
  234. return;
  235. int y = ((viewHeight - bottomPadding - topPadding) - itemHeight) >> 1;
  236. if (y < 0)
  237. y = 0;
  238. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
  239. smoothScroll(position, y);
  240. else
  241. setSelectionFromTop(position, y);
  242. }
  243. public void centerItem(int position) {
  244. //do not change to itemCount!
  245. if (position < 0 || adapter == null || position >= adapter.getCount())
  246. return;
  247. int y = ((viewHeight - bottomPadding - topPadding) - itemHeight) >> 1;
  248. if (y < 0)
  249. y = 0;
  250. setSelectionFromTop(position, y);
  251. }
  252. public View getViewForPosition(int position) {
  253. position -= getFirstVisiblePosition();
  254. if (position < 0 || position >= getChildCount())
  255. return null;
  256. return getChildAt(position);
  257. }
  258. public void setOnKeyDownObserver(OnBgListViewKeyDownObserver keyDownObserver) {
  259. this.keyDownObserver = keyDownObserver;
  260. }
  261. public void notifyMeWhenFirstAttached(OnAttachedObserver observer) {
  262. attachedObserver = observer;
  263. if (attached && measured && sized && attachedObserver != null) {
  264. notified = true;
  265. attachedObserver.onBgListViewAttached(this);
  266. attachedObserver = null;
  267. }
  268. }
  269. public void setEmptyListOnClickListener(OnClickListener listener) {
  270. emptyListClickListener = listener;
  271. }
  272. public void setCustomEmptyText(CharSequence text) {
  273. if (text == null) {
  274. emptyLayout = null;
  275. } else {
  276. UI.textPaint.setTextSize(UI._22sp);
  277. emptyLayout = new StaticLayout(text, UI.textPaint, (viewWidth < (UI.controlLargeMargin << 1)) ? 0 : (viewWidth - (UI.controlLargeMargin << 1)), Alignment.ALIGN_CENTER, 1, 0, false);
  278. }
  279. }
  280. @Override
  281. protected void onAttachedToWindow() {
  282. ignorePadding = true;
  283. super.onAttachedToWindow();
  284. attached = true;
  285. if (!notified && measured && sized && attachedObserver != null) {
  286. notified = true;
  287. attachedObserver.onBgListViewAttached(this);
  288. attachedObserver = null;
  289. }
  290. ignorePadding = false;
  291. }
  292. @Override
  293. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  294. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  295. measured = true;
  296. if (!notified && attached && sized && attachedObserver != null) {
  297. notified = true;
  298. attachedObserver.onBgListViewAttached(this);
  299. attachedObserver = null;
  300. }
  301. }
  302. @Override
  303. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  304. super.onSizeChanged(w, h, oldw, oldh);
  305. if (w > 0 && h > 0) {
  306. viewWidth = w;
  307. viewHeight = h;
  308. if (!UI.scrollBarToTheLeft)
  309. //scrollBarLeft = w - (rightPadding + scrollBarWidth);
  310. scrollBarLeft = w - scrollBarWidth;
  311. sized = true;
  312. if (!notified && attached && measured && attachedObserver != null) {
  313. notified = true;
  314. attachedObserver.onBgListViewAttached(this);
  315. attachedObserver = null;
  316. }
  317. if (emptyLayout != null && emptyLayout.getWidth() != w)
  318. setCustomEmptyText(emptyLayout.getText());
  319. switch (scrollBarType) {
  320. case SCROLLBAR_LARGE:
  321. scrollBarTop = topPadding + UI.controlSmallMargin;
  322. scrollBarBottom = h - bottomPadding - UI.controlSmallMargin;
  323. updateScrollBarThumb();
  324. break;
  325. case SCROLLBAR_INDEXED:
  326. scrollBarTop = topPadding;
  327. scrollBarBottom = h - bottomPadding;
  328. updateScrollBarIndices(false);
  329. break;
  330. }
  331. }
  332. }
  333. public int getNewPosition(int position, int keyCode, boolean allowWrap) {
  334. int p;
  335. final int l = itemCount - 1;
  336. switch (keyCode) {
  337. case UI.KEY_UP:
  338. case UI.KEY_LEFT:
  339. if (position > l || position < 0 || (allowWrap && position == 0))
  340. return l;
  341. return position - 1;
  342. case UI.KEY_DOWN:
  343. case UI.KEY_RIGHT:
  344. if ((allowWrap && position == l) || position > l || position < 0)
  345. return 0;
  346. return position + 1;
  347. case UI.KEY_PAGE_UP:
  348. if (position > l || position < 0 || (allowWrap && position == 0))
  349. return l;
  350. p = getLastVisiblePosition() - getFirstVisiblePosition();
  351. if (p > 1)
  352. p = position - (p - 1);
  353. else
  354. p = position - 1;
  355. return ((p <= 0) ? 0 : p);
  356. case UI.KEY_PAGE_DOWN:
  357. if ((allowWrap && position == l) || position > l || position < 0)
  358. return 0;
  359. p = getLastVisiblePosition() - getFirstVisiblePosition();
  360. if (p > 1)
  361. p = position + p - 1;
  362. else
  363. p = position + 1;
  364. return ((p >= l) ? l : p);
  365. case UI.KEY_HOME:
  366. return 0;
  367. case UI.KEY_END:
  368. return l;
  369. }
  370. return -1;
  371. }
  372. private void invalidateScrollBar() {
  373. invalidate(scrollBarLeft, scrollBarTop, scrollBarLeft + scrollBarWidth, scrollBarBottom);
  374. }
  375. private void cancelTracking() {
  376. if (tracking) {
  377. tracking = false;
  378. if (attached)
  379. super.onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 10, MotionEvent.ACTION_CANCEL, 0, 0, 0));
  380. if (scrollBarType == SCROLLBAR_INDEXED)
  381. invalidate();
  382. else
  383. invalidateScrollBar();
  384. }
  385. }
  386. private void setSelectionAtTheTop(int position) {
  387. if (getFirstVisiblePosition() == position) {
  388. final View firstChild = getChildAt(0);
  389. if (firstChild != null && firstChild.getTop() == 0)
  390. return;
  391. }
  392. setSelectionFromTop(position, 0);
  393. }
  394. private void trackTouchEvent(int y) {
  395. if (adapter == null)
  396. return;
  397. final int count = adapter.getCount();
  398. if (count <= 0)
  399. return;
  400. final int sbh = (scrollBarBottom - scrollBarTop), vh = (viewHeight - bottomPadding - topPadding);
  401. y -= scrollBarThumbOffset + scrollBarTop;
  402. int f;
  403. if (y <= 0) {
  404. scrollBarThumbTop = scrollBarTop;
  405. f = 0;
  406. } else if (y >= (sbh - scrollBarThumbHeight)) {
  407. scrollBarThumbTop = sbh - scrollBarThumbHeight + scrollBarTop;
  408. f = count - 1;
  409. } else {
  410. scrollBarThumbTop = y + scrollBarTop;
  411. int t = (scrollBarThumbTop * (contentsHeight - vh)) / (sbh - scrollBarThumbHeight);
  412. f = t / itemHeight;
  413. if (f >= count)
  414. f = count - 1;
  415. }
  416. invalidateScrollBar();
  417. setSelectionAtTheTop(f);
  418. }
  419. private void trackIndexedTouchEvent(int y) {
  420. if (scrollBarThumbHeight == 0 || sectionPositions == null)
  421. return;
  422. y = (y - scrollBarTop) / scrollBarThumbHeight;
  423. if (y < 0)
  424. y = 0;
  425. if (y >= sectionPositions.length)
  426. y = sectionPositions.length - 1;
  427. if (scrollBarThumbTop == y || y < 0)
  428. return;
  429. scrollBarThumbTop = y;
  430. y = sectionPositions[y];
  431. invalidateScrollBar();
  432. if (adapter != null && y >= 0 && y < adapter.getCount())
  433. setSelectionAtTheTop(y);
  434. }
  435. @Override
  436. public boolean onTouchEvent(@NonNull MotionEvent event) {
  437. switch (event.getAction()) {
  438. case MotionEvent.ACTION_DOWN:
  439. if (scrollBarThumbHeight == 0) break;
  440. final int x = (int)event.getX();
  441. if (x >= scrollBarLeft && x < (scrollBarLeft + scrollBarWidth)) {
  442. tracking = true;
  443. if (scrollState == SCROLL_STATE_FLING) {
  444. //simulate a simple 10ms tap, to stop the fling
  445. super.onTouchEvent(event);
  446. super.onTouchEvent(MotionEvent.obtain(event.getDownTime(), event.getDownTime() + 10, MotionEvent.ACTION_UP, event.getX(), event.getY(), event.getMetaState()));
  447. scrollState = SCROLL_STATE_IDLE;
  448. }
  449. final int y = (int)event.getY();
  450. if (scrollBarType == SCROLLBAR_LARGE) {
  451. if (y < scrollBarThumbTop || y >= (scrollBarThumbTop + scrollBarThumbHeight))
  452. scrollBarThumbOffset = scrollBarThumbHeight >> 1;
  453. else
  454. scrollBarThumbOffset = y - scrollBarThumbTop;
  455. trackTouchEvent(y);
  456. } else {
  457. invalidate();
  458. scrollBarThumbTop = -1;
  459. trackIndexedTouchEvent(y);
  460. }
  461. if (getParent() != null)
  462. getParent().requestDisallowInterceptTouchEvent(true);
  463. playSoundEffect(SoundEffectConstants.CLICK);
  464. return true;
  465. }
  466. break;
  467. case MotionEvent.ACTION_MOVE:
  468. if (!tracking)
  469. break;
  470. if (scrollBarType == SCROLLBAR_LARGE)
  471. trackTouchEvent((int)event.getY());
  472. else
  473. trackIndexedTouchEvent((int)event.getY());
  474. return true;
  475. case MotionEvent.ACTION_UP:
  476. if (tracking) {
  477. tracking = false;
  478. if (scrollBarType == SCROLLBAR_LARGE)
  479. trackTouchEvent((int)event.getY());
  480. else
  481. invalidate();
  482. return true;
  483. } else if (emptyListClickListener != null && itemCount == 0) {
  484. playSoundEffect(SoundEffectConstants.CLICK);
  485. emptyListClickListener.onClick(this);
  486. }
  487. break;
  488. case MotionEvent.ACTION_CANCEL:
  489. if (tracking) {
  490. tracking = false;
  491. if (scrollBarType == SCROLLBAR_LARGE)
  492. invalidateScrollBar();
  493. else
  494. invalidate();
  495. }
  496. break;
  497. }
  498. return super.onTouchEvent(event);
  499. }
  500. private void updateScrollBarSectionForFirstPosition() {
  501. final int first = getFirstVisiblePosition();
  502. if (first <= 0 || sections == null) {
  503. scrollBarThumbTop = 0;
  504. return;
  505. }
  506. int s = 0, e = sections.length - 1, m = 0;
  507. while (s <= e) {
  508. m = ((e + s) >> 1);
  509. if (first == sectionPositions[m]) {
  510. scrollBarThumbTop = m;
  511. return;
  512. }
  513. else if (first < sectionPositions[m])
  514. e = m - 1;
  515. else
  516. s = m + 1;
  517. }
  518. if (first < sectionPositions[m])
  519. m--;
  520. if (m < 0)
  521. m = 0;
  522. else if (m >= sections.length)
  523. m = sections.length - 1;
  524. scrollBarThumbTop = m;
  525. }
  526. private void updateScrollBarThumb() {
  527. final int sbh = (scrollBarBottom - scrollBarTop);
  528. if (itemCount == 0 || sbh <= 0) {
  529. scrollBarThumbHeight = 0;
  530. } else {
  531. final View v = getChildAt(0);
  532. if (v == null) {
  533. scrollBarThumbHeight = 0;
  534. return;
  535. }
  536. final int vh = (viewHeight - bottomPadding - topPadding);
  537. contentsHeight = (itemCount * itemHeight);
  538. if (contentsHeight <= vh) {
  539. scrollBarThumbHeight = 0;
  540. return;
  541. }
  542. scrollBarThumbHeight = (sbh * vh) / contentsHeight;
  543. if (scrollBarThumbHeight < UI.controlMargin)
  544. scrollBarThumbHeight = UI.controlMargin;
  545. scrollBarThumbTop = scrollBarTop + (((sbh - scrollBarThumbHeight) * ((getFirstVisiblePosition() * itemHeight) - v.getTop())) / (contentsHeight - vh));
  546. }
  547. }
  548. private void updateScrollBarIndices(boolean updateEverything) {
  549. if (updateEverything) {
  550. if (adapter == null || !(adapter instanceof BaseList.BaseSectionIndexer)) {
  551. sections = null;
  552. sectionPositions = null;
  553. } else {
  554. final BaseList.BaseSectionIndexer a = (BaseList.BaseSectionIndexer)adapter;
  555. sections = a.getSectionStrings();
  556. sectionPositions = a.getSectionPositions();
  557. if (sections == null || sectionPositions == null || sections.length != sectionPositions.length || sections.length == 0) {
  558. sections = null;
  559. sectionPositions = null;
  560. }
  561. }
  562. }
  563. if (sections != null) {
  564. //let's reuse a few variables... ;)
  565. scrollBarThumbHeight = (scrollBarBottom - scrollBarTop) / sections.length;
  566. contentsHeight = scrollBarThumbHeight - (UI._1dp << 1);
  567. if (contentsHeight > (scrollBarWidth - UI._1dp))
  568. contentsHeight = (scrollBarWidth - UI._1dp);
  569. if (contentsHeight < (UI._1dp << 1))
  570. contentsHeight = (UI._1dp << 1);
  571. UI.textPaint.setTextSize(contentsHeight);
  572. final Paint.FontMetrics fm = UI.textPaint.getFontMetrics();
  573. final int box = (int)(fm.descent - fm.ascent + 0.5f);
  574. scrollBarThumbOffset = ((scrollBarThumbHeight - box) >> 1) + (box - (int)fm.descent);
  575. } else {
  576. scrollBarThumbHeight = 0;
  577. contentsHeight = 0;
  578. scrollBarThumbOffset = 0;
  579. }
  580. updateScrollBarSectionForFirstPosition();
  581. }
  582. @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  583. private void setVerticalScrollBarPosition() {
  584. super.setVerticalScrollbarPosition(UI.scrollBarToTheLeft ? SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT);
  585. }
  586. public void setScrollBarType(int scrollBarType) {
  587. cancelTracking();
  588. switch (scrollBarType) {
  589. case SCROLLBAR_LARGE:
  590. if (this.scrollBarType == SCROLLBAR_LARGE)
  591. return;
  592. sections = null;
  593. sectionPositions = null;
  594. ignorePadding = true;
  595. super.setVerticalScrollBarEnabled(false);
  596. super.setOnScrollListener(this);
  597. ignorePadding = false;
  598. this.scrollBarType = SCROLLBAR_LARGE;
  599. scrollBarTop = topPadding + UI.controlSmallMargin;
  600. scrollBarBottom = viewHeight - bottomPadding - UI.controlSmallMargin;
  601. scrollBarWidth = (UI.defaultControlSize >> 1);
  602. updateScrollBarThumb();
  603. break;
  604. case SCROLLBAR_INDEXED:
  605. if (this.scrollBarType == SCROLLBAR_INDEXED)
  606. return;
  607. ignorePadding = true;
  608. super.setVerticalScrollBarEnabled(false);
  609. super.setOnScrollListener(this);
  610. ignorePadding = false;
  611. this.scrollBarType = SCROLLBAR_INDEXED;
  612. scrollBarTop = topPadding;
  613. scrollBarBottom = viewHeight - bottomPadding;
  614. scrollBarWidth = (UI.defaultControlSize >> 1);
  615. updateScrollBarIndices(true);
  616. break;
  617. case SCROLLBAR_NONE:
  618. if (this.scrollBarType == SCROLLBAR_NONE)
  619. return;
  620. sections = null;
  621. sectionPositions = null;
  622. ignorePadding = true;
  623. super.setOnScrollListener(null);
  624. super.setVerticalScrollBarEnabled(false);
  625. ignorePadding = false;
  626. this.scrollBarType = SCROLLBAR_NONE;
  627. scrollBarWidth = 0;
  628. break;
  629. default:
  630. if (this.scrollBarType == SCROLLBAR_SYSTEM)
  631. return;
  632. sections = null;
  633. sectionPositions = null;
  634. ignorePadding = true;
  635. super.setOnScrollListener(null);
  636. super.setVerticalScrollBarEnabled(true);
  637. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
  638. setVerticalScrollBarPosition();
  639. ignorePadding = false;
  640. this.scrollBarType = SCROLLBAR_SYSTEM;
  641. scrollBarWidth = 0;
  642. break;
  643. }
  644. ignorePadding = true;
  645. if (UI.scrollBarToTheLeft) {
  646. scrollBarLeft = 0;//leftPadding;
  647. super.setPadding(leftPadding + scrollBarWidth, topPadding, rightPadding, bottomPadding);
  648. } else {
  649. scrollBarLeft = viewWidth - scrollBarWidth;//(viewWidth - (rightPadding + scrollBarWidth));
  650. super.setPadding(leftPadding, topPadding, rightPadding + scrollBarWidth, bottomPadding);
  651. }
  652. ignorePadding = false;
  653. UI.removeInternalPaddingForEdgeEffect(this);
  654. }
  655. @Override
  656. public void setPadding(int left, int top, int right, int bottom) {
  657. if (ignorePadding)
  658. return;
  659. if (UI.scrollBarToTheLeft) {
  660. scrollBarLeft = 0;
  661. super.setPadding((leftPadding = left) + scrollBarWidth, (topPadding = top), (rightPadding = right), (bottomPadding = bottom));
  662. } else {
  663. scrollBarLeft = viewWidth - scrollBarWidth;
  664. super.setPadding((leftPadding = left), (topPadding = top), (rightPadding = right) + scrollBarWidth, (bottomPadding = bottom));
  665. }
  666. UI.removeInternalPaddingForEdgeEffect(this);
  667. }
  668. private void defaultKeyDown(int keyCode) {
  669. if (adapter == null)
  670. return;
  671. final int s = getNewPosition(adapter.getSelection(), keyCode, true);
  672. if (s >= 0 && s < itemCount) {
  673. adapter.setSelection(s, true);
  674. if (s <= getFirstVisiblePosition() || s >= getLastVisiblePosition())
  675. centerItem(s);
  676. }
  677. }
  678. @Override
  679. public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
  680. if (isEnabled()) {
  681. switch (keyCode) {
  682. case UI.KEY_UP:
  683. case UI.KEY_DOWN:
  684. if (skipUpDownTranslation)
  685. break;
  686. //change the key to make sure the focus goes
  687. //somewhere else when the list is empty, or when the
  688. //selection is not set to wrap around and it is at
  689. //the top/bottom of the list
  690. if (adapter == null || itemCount == 0) {
  691. keyCode = ((keyCode == UI.KEY_UP) ? UI.KEY_LEFT : UI.KEY_RIGHT);
  692. } else if (!UI.wrapAroundList) {
  693. if (keyCode == UI.KEY_UP) {
  694. if (adapter.getSelection() == 0)
  695. keyCode = (UI.KEY_LEFT);
  696. } else if (adapter.getSelection() == (itemCount - 1)) {
  697. keyCode = (UI.KEY_RIGHT);
  698. }
  699. }
  700. break;
  701. case UI.KEY_LEFT:
  702. case UI.KEY_RIGHT:
  703. case UI.KEY_DEL:
  704. case UI.KEY_EXTRA:
  705. case UI.KEY_HOME:
  706. case UI.KEY_END:
  707. case UI.KEY_PAGE_UP:
  708. case UI.KEY_PAGE_DOWN:
  709. break;
  710. case KeyEvent.KEYCODE_DPAD_CENTER:
  711. case KeyEvent.KEYCODE_ENTER:
  712. case KeyEvent.KEYCODE_NUMPAD_ENTER:
  713. case KeyEvent.KEYCODE_BUTTON_START:
  714. case KeyEvent.KEYCODE_BUTTON_A:
  715. case KeyEvent.KEYCODE_BUTTON_B:
  716. keyCode = UI.KEY_ENTER;
  717. if (emptyListClickListener != null && itemCount == 0)
  718. emptyListClickListener.onClick(this);
  719. break;
  720. case KeyEvent.KEYCODE_DEL:
  721. keyCode = UI.KEY_DEL;
  722. break;
  723. case KeyEvent.KEYCODE_BUTTON_SELECT:
  724. case KeyEvent.KEYCODE_BUTTON_X:
  725. case KeyEvent.KEYCODE_BUTTON_Y:
  726. keyCode = UI.KEY_EXTRA;
  727. break;
  728. default:
  729. if ((keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) || (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9))
  730. keyCode = UI.KEY_EXTRA;
  731. else
  732. return super.onKeyDown(keyCode, event);
  733. break;
  734. }
  735. if (keyDownObserver == null || !keyDownObserver.onBgListViewKeyDown(this, keyCode))
  736. defaultKeyDown(keyCode);
  737. return true;
  738. } else {
  739. return super.onKeyDown(keyCode, event);
  740. }
  741. }
  742. /*@Override
  743. protected void dispatchDraw(Canvas canvas) {
  744. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  745. super.dispatchDraw(canvas);
  746. } else {
  747. //LAME!!!!! There is a bug in API 10 and the padding
  748. //IS NOT properly accounted for and the children are not clipped!!!
  749. canvas.save();
  750. getDrawingRect(UI.rect);
  751. UI.rect.top += getPaddingTop();
  752. UI.rect.bottom -= getPaddingBottom();
  753. canvas.clipRect(UI.rect);
  754. super.dispatchDraw(canvas);
  755. canvas.restore();
  756. }
  757. }*/
  758. @SuppressWarnings("unchecked")
  759. @Override
  760. public void setAdapter(ListAdapter adapter) {
  761. this.adapter = (BaseList<? extends BaseItem>)adapter;
  762. itemHeight = ((adapter == null) ? UI.defaultControlSize : this.adapter.getViewHeight());
  763. cancelTracking();
  764. itemCount = ((adapter == null) ? 0 : adapter.getCount());
  765. switch (scrollBarType) {
  766. case SCROLLBAR_LARGE:
  767. updateScrollBarThumb();
  768. break;
  769. case SCROLLBAR_INDEXED:
  770. updateScrollBarIndices(true);
  771. break;
  772. }
  773. super.setAdapter(adapter);
  774. }
  775. @Override
  776. protected void handleDataChanged() {
  777. final int last = itemCount;
  778. itemCount = ((adapter == null) ? 0 : adapter.getCount());
  779. if (itemCount == 0 || last > itemCount)
  780. cancelTracking();
  781. switch (scrollBarType) {
  782. case SCROLLBAR_LARGE:
  783. updateScrollBarThumb();
  784. break;
  785. case SCROLLBAR_INDEXED:
  786. updateScrollBarIndices(true);
  787. break;
  788. }
  789. super.handleDataChanged();
  790. }
  791. @Override
  792. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  793. if (tracking)
  794. return;
  795. switch (scrollBarType) {
  796. case SCROLLBAR_LARGE:
  797. updateScrollBarThumb();
  798. break;
  799. case SCROLLBAR_INDEXED:
  800. updateScrollBarSectionForFirstPosition();
  801. break;
  802. }
  803. }
  804. @Override
  805. public void onScrollStateChanged(AbsListView view, int scrollState) {
  806. this.scrollState = scrollState;
  807. }
  808. @Override
  809. protected void dispatchDraw(@NonNull Canvas canvas) {
  810. super.dispatchDraw(canvas);
  811. if (itemCount == 0) {
  812. if (emptyLayout != null) {
  813. final float x = (float)((viewWidth - emptyLayout.getWidth()) >> 1);
  814. final float y = (float)((viewHeight - emptyLayout.getHeight()) >> 1);
  815. canvas.translate(x, y);
  816. UI.textPaint.setColor(UI.color_text_disabled);
  817. UI.textPaint.setTextSize(UI._22sp);
  818. emptyLayout.draw(canvas);
  819. canvas.translate(-x, -y);
  820. }
  821. } else {
  822. switch (scrollBarType) {
  823. case SCROLLBAR_INDEXED:
  824. if (sections != null) {
  825. UI.textPaint.setColor(UI.color_text_highlight);
  826. UI.textPaint.setTextSize(contentsHeight);
  827. UI.textPaint.setTextAlign(Paint.Align.CENTER);
  828. final float l;
  829. if (tracking) {
  830. if (UI.scrollBarToTheLeft) {
  831. UI.rect.left = scrollBarLeft;
  832. l = (float)(UI.rect.left + UI.defaultControlContentsSize + (UI.defaultControlContentsSize >> 1) + (scrollBarWidth >> 1));
  833. } else {
  834. UI.rect.left = scrollBarLeft - UI.defaultControlContentsSize - (UI.defaultControlContentsSize >> 1);
  835. l = (float)(UI.rect.left + (scrollBarWidth >> 1));
  836. }
  837. UI.rect.right = UI.rect.left + UI.defaultControlContentsSize + (UI.defaultControlContentsSize >> 1) + scrollBarWidth;
  838. } else {
  839. UI.rect.left = scrollBarLeft;
  840. UI.rect.right = scrollBarLeft + scrollBarWidth;
  841. l = (float)(scrollBarLeft + (scrollBarWidth >> 1));
  842. }
  843. UI.rect.top = scrollBarTop;
  844. UI.rect.bottom = scrollBarBottom;
  845. UI.fillRect(canvas, UI.color_highlight);
  846. int i;
  847. for (i = 0; i < scrollBarThumbTop; i++) {
  848. canvas.drawText(sections[i], l, (float)(UI.rect.top + scrollBarThumbOffset), UI.textPaint);
  849. UI.rect.top += scrollBarThumbHeight;
  850. }
  851. UI.rect.bottom = UI.rect.top + scrollBarThumbHeight;
  852. UI.textPaint.setColor(UI.color_text_listitem);//UI.color_text_selected);
  853. UI.fillRect(canvas, UI.color_list);//UI.color_selected_pressed);
  854. //UI.strokeRect(canvas, UI.color_selected_pressed_border, UI.strokeSize);
  855. canvas.drawText(sections[i], l, (float)(UI.rect.top + scrollBarThumbOffset), UI.textPaint);
  856. UI.textPaint.setColor(UI.color_text_highlight);
  857. UI.rect.top = UI.rect.bottom;
  858. i++;
  859. for (; i < sections.length; i++) {
  860. canvas.drawText(sections[i], l, (float)(UI.rect.top + scrollBarThumbOffset), UI.textPaint);
  861. UI.rect.top += scrollBarThumbHeight;
  862. }
  863. UI.textPaint.setTextAlign(Paint.Align.LEFT);
  864. }
  865. break;
  866. case SCROLLBAR_LARGE:
  867. UI.rect.left = scrollBarLeft + ((scrollBarWidth - UI.strokeSize) >> 1);
  868. UI.rect.right = UI.rect.left + UI.strokeSize;
  869. UI.rect.top = scrollBarTop + UI.controlSmallMargin;
  870. UI.rect.bottom = scrollBarBottom - UI.controlSmallMargin;
  871. UI.fillRect(canvas, UI.color_divider);
  872. if (scrollBarThumbHeight > 0) {
  873. UI.rect.left = scrollBarLeft + UI.controlSmallMargin;
  874. UI.rect.top = scrollBarThumbTop;
  875. UI.rect.right = scrollBarLeft + scrollBarWidth - UI.controlSmallMargin;
  876. UI.rect.bottom = UI.rect.top + scrollBarThumbHeight;
  877. if (tracking) {
  878. UI.fillRect(canvas, UI.color_divider_pressed);
  879. //UI.strokeRect(canvas, UI.color_selected_pressed_border, UI.strokeSize);
  880. } else {
  881. UI.fillRect(canvas, UI.color_divider);
  882. }
  883. }
  884. break;
  885. }
  886. }
  887. }
  888. @Override
  889. public void onDraw(Canvas canvas) {
  890. //there is no need to call super.onDraw() here, as ListView does
  891. //not override this method... therefore, it does nothing!
  892. //super.onDraw(canvas);
  893. }
  894. @Override
  895. public void onWindowFocusChanged(boolean hasWindowFocus) {
  896. cancelTracking();
  897. super.onWindowFocusChanged(hasWindowFocus);
  898. }
  899. @Override
  900. protected void onDetachedFromWindow() {
  901. attachedObserver = null;
  902. keyDownObserver = null;
  903. emptyListClickListener = null;
  904. emptyLayout = null;
  905. sections = null;
  906. sectionPositions = null;
  907. setAdapter(null);
  908. super.onDetachedFromWindow();
  909. }
  910. }