完善阅读界面
This commit is contained in:
1 parent
a2bbe855e8
commit
e451db6032
16 files changed
+822
-88
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -14,7 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -25,4 +25,70 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</RelativeLayout>
|
||||
<com.hippo.ehviewer.widget.TextClock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:textColor="@color/secondary_text_light" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/action_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="top"
|
||||
android:background="@color/background_little_dark"
|
||||
android:orientation="horizontal"
|
||||
android:clickable="true"
|
||||
android:visibility="gone" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="24.0dp"
|
||||
android:layout_marginRight="24.0dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/secondary_text_light"
|
||||
android:textSize="@dimen/text_large" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/nav_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@color/background_little_dark"
|
||||
android:orientation="horizontal"
|
||||
android:clickable="true"
|
||||
android:visibility="gone" >
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/page_seeker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/seeker_bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="52dp"
|
||||
android:ellipsize="start"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/secondary_text_light"
|
||||
android:textSize="@dimen/text_large"
|
||||
android:visibility="gone"
|
||||
android:background="@drawable/seeker_bubble" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -26,6 +26,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/content" />
|
||||
|
||||
@@ -35,6 +36,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="4"
|
||||
android:layout_gravity="center"
|
||||
android:entries="@array/mode_list" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -48,6 +50,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/api" />
|
||||
|
||||
@@ -57,7 +60,31 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="4"
|
||||
android:layout_gravity="center"
|
||||
android:entries="@array/api_mode_list" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0.0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="Lofi 分辨率" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/lofi_resolution_list"
|
||||
android:layout_width="0.0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_weight="4"
|
||||
android:layout_gravity="center"
|
||||
android:entries="@array/lofi_resolution_list" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -33,6 +33,12 @@
|
||||
<item>exhentai.org</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="lofi_resolution_list" translatable="false">
|
||||
<item>460x</item>
|
||||
<item>780x</item>
|
||||
<item>980x</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="comment_long_click">
|
||||
<item>@android:string/copy</item>
|
||||
<item>@string/uploader_others</item>
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<color name="background_light">#fffafafa</color>
|
||||
<color name="background_dark">#ff212121</color>
|
||||
<color name="background_little_light">#ffe5e5e5</color>
|
||||
<color name="background_little_dark">#ff353535</color>
|
||||
|
||||
<color name="primary_text_dark">#de000000</color>
|
||||
<color name="secondary_text_dark">#8a000000</color>
|
||||
|
||||
@@ -173,7 +173,8 @@ public class EhInfo {
|
||||
public void setCookie(HttpURLConnection conn, String previewMode) {
|
||||
String cookie = "ipb_member_id=" + mInfoPref.getString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID) +
|
||||
"; ipb_pass_hash=" + mInfoPref.getString(KEY_PASS_HASH, DEFAULT_PASS_HASH) +
|
||||
"; uconfig=" + (previewMode == null ? mUconfig : getUconfigString(previewMode));
|
||||
"; uconfig=" + (previewMode == null ? mUconfig : getUconfigString(previewMode)) +
|
||||
"; xres=" + Config.getLofiResolution();
|
||||
conn.setRequestProperty("Cookie", cookie);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,9 +192,9 @@ public class ExDownloader implements Runnable {
|
||||
if (mImageNum != -1) {
|
||||
return mImageNum;
|
||||
} else {
|
||||
return Math.max(Math.max(mStartIndex - 1,
|
||||
return Math.max(Math.max(Math.max(mStartIndex - 1,
|
||||
Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage),
|
||||
mPageTokeArray.maxValidIndex());
|
||||
mPageTokeArray.maxValidIndex()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,6 @@ public class ExDownloader implements Runnable {
|
||||
|
||||
// A loop to get page token
|
||||
while (true) {
|
||||
|
||||
// Check stop
|
||||
if (mStopWork)
|
||||
break;
|
||||
@@ -448,9 +447,6 @@ public class ExDownloader implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "noTokenIndex = " + noTokenIndex);
|
||||
Log.d(TAG, "pageIndex = " + pageIndex);
|
||||
|
||||
if (noTokenIndex != -1) {
|
||||
if (mPageTokeArray.get(noTokenIndex) == null) {
|
||||
getDetailInfo(noTokenIndex / mPreviewPerPage, ediFile, false);
|
||||
@@ -462,6 +458,8 @@ public class ExDownloader implements Runnable {
|
||||
mCurRequestPageIndex = -1;
|
||||
}
|
||||
|
||||
ensureWorkers();
|
||||
|
||||
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
|
||||
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.hippo.ehviewer.util.MathUtils;
|
||||
import com.hippo.ehviewer.util.TimeRunner;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
import com.hippo.ehviewer.widget.MaterialToast;
|
||||
|
||||
// TODO 双击加载该页
|
||||
// TODO 长按从该页开始加载
|
||||
@@ -126,12 +127,9 @@ public class GalleryView extends GLView
|
||||
private int scrollXOffset = 0;
|
||||
private int scrollYOffset = 0;
|
||||
private int stopScrollXOffset = 0;
|
||||
@SuppressWarnings("unused")
|
||||
private int stopScrollYOffset = 0;
|
||||
private float mScale = 1;
|
||||
|
||||
private boolean mShowEdegTip = true;
|
||||
|
||||
private int mWidth = -1;
|
||||
private int mHeight = -1;
|
||||
|
||||
@@ -157,13 +155,16 @@ public class GalleryView extends GLView
|
||||
private boolean mShowTapArea = false;
|
||||
private boolean mShowTapAreaTurn = false;
|
||||
|
||||
private OnEdgeListener mOnEdgeListener;
|
||||
private OnTapTextListener mOnTapTextListener;
|
||||
private OnScrollPageListener mOnScrollPageListener;
|
||||
//private boolean mLocked = false;
|
||||
//private boolean mLocked = false;
|
||||
|
||||
public interface OnEdgeListener {
|
||||
public void onFirstPageEdge();
|
||||
public void onLastPageEdge();
|
||||
private OnTapTextListener mOnTapTextListener;
|
||||
private GalleryViewListener mGalleryViewListener;
|
||||
|
||||
public interface GalleryViewListener {
|
||||
public void onTapCenter();
|
||||
public void onPageChanged(int index);
|
||||
public void onSizeUpdate(int size);
|
||||
}
|
||||
|
||||
public interface OnTapTextListener {
|
||||
@@ -171,20 +172,12 @@ public class GalleryView extends GLView
|
||||
public void onTapDoubleText(int index);
|
||||
}
|
||||
|
||||
public interface OnScrollPageListener {
|
||||
public void onScrollPage(int index);
|
||||
}
|
||||
|
||||
public void setOnEdgeListener(OnEdgeListener l) {
|
||||
mOnEdgeListener = l;
|
||||
}
|
||||
|
||||
public void setOnTapTextListener(OnTapTextListener l) {
|
||||
mOnTapTextListener = l;
|
||||
}
|
||||
|
||||
public void setOnScrollPageListener(OnScrollPageListener l) {
|
||||
mOnScrollPageListener = l;
|
||||
public void setGalleryViewListener(GalleryViewListener l) {
|
||||
mGalleryViewListener = l;
|
||||
}
|
||||
|
||||
public GalleryView(Context context, ImageSet imageSet, int startIndex) {
|
||||
@@ -224,6 +217,7 @@ public class GalleryView extends GLView
|
||||
@Override
|
||||
public void run() {
|
||||
checkSize();
|
||||
setState();
|
||||
if (!isEnsureSize && !mImageSet.isStop())
|
||||
AppHandler.getInstance().postDelayed(this, 500);
|
||||
}
|
||||
@@ -232,6 +226,7 @@ public class GalleryView extends GLView
|
||||
* Check size
|
||||
*/
|
||||
private void checkSize() {
|
||||
int oldSize = mSize;
|
||||
int size = mImageSet.getSize();
|
||||
if (size == -1) {
|
||||
isEnsureSize = false;
|
||||
@@ -240,6 +235,9 @@ public class GalleryView extends GLView
|
||||
isEnsureSize = true;
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
if (mSize != oldSize && mGalleryViewListener != null)
|
||||
mGalleryViewListener.onSizeUpdate(mSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,28 +251,31 @@ public class GalleryView extends GLView
|
||||
mState = STATE_LAST;
|
||||
}
|
||||
|
||||
private boolean checkIndex(int index) {
|
||||
private boolean isIndexLoadable(int index) {
|
||||
int targetIndex = index - mCurIndex + 1;
|
||||
return targetIndex == CUR_TARGET_INDEX ||
|
||||
(mState != STATE_FIRST && targetIndex == PRE_TARGET_INDEX) ||
|
||||
(mState != STATE_LAST && targetIndex == NEXT_TARGET_INDEX);
|
||||
}
|
||||
|
||||
private boolean isIndexValid(int index) {
|
||||
return index >= 0 && index < mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetImage(int index) {
|
||||
if (checkIndex(index)) {
|
||||
if (isIndexLoadable(index)) {
|
||||
loadImage(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloading(int index, float percent) {
|
||||
public synchronized void onDownloading(int index, float percent) {
|
||||
int targetIndex = index - mCurIndex + 1;
|
||||
if (checkIndex(index)) {
|
||||
if (isIndexLoadable(index)) {
|
||||
// Free what do not need
|
||||
ShowItem showItem = showItems[targetIndex];
|
||||
if (showItem != null)
|
||||
showItem.recycle();
|
||||
if (showItems[targetIndex] != null)
|
||||
showItems[targetIndex].recycle();
|
||||
|
||||
showItems[targetIndex] = new Text(
|
||||
String.format("正在加载 %d%%", Math.round(percent * 100)));
|
||||
@@ -283,18 +284,17 @@ public class GalleryView extends GLView
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecodeOver(int index, Object res) {
|
||||
public synchronized void onDecodeOver(int index, Object res) {
|
||||
int targetIndex = index - mCurIndex + 1;
|
||||
|
||||
if (!checkIndex(index)) {
|
||||
if (!isIndexLoadable(index)) {
|
||||
// If it do not need any more, free
|
||||
if (res != null && res instanceof Bitmap)
|
||||
((Bitmap)res).recycle();
|
||||
} else {
|
||||
// Free what do not need
|
||||
ShowItem showItem = showItems[targetIndex];
|
||||
if (showItem != null)
|
||||
showItem.recycle();
|
||||
if (showItems[targetIndex] != null)
|
||||
showItems[targetIndex].recycle();
|
||||
|
||||
if (res == null) {
|
||||
showItems[targetIndex] = new Text("读取图片错误"); // TODO
|
||||
@@ -313,6 +313,14 @@ public class GalleryView extends GLView
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurIndex() {
|
||||
return mCurIndex;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
private void drawTapArea(GLCanvas canvas) {
|
||||
// Background
|
||||
canvas.fillRect(0, 0, mWidth, mHeight, TAP_AREA_MASK_COLOR);
|
||||
@@ -344,7 +352,8 @@ public class GalleryView extends GLView
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(GLCanvas canvas) {
|
||||
protected synchronized void render(GLCanvas canvas) {
|
||||
// If it is not render thread, do not render
|
||||
super.render(canvas);
|
||||
|
||||
boolean hasMovie = false;
|
||||
@@ -622,8 +631,8 @@ public class GalleryView extends GLView
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
private void loadImage(int index) {
|
||||
if (!checkIndex(index))
|
||||
private synchronized void loadImage(int index) {
|
||||
if (!isIndexLoadable(index))
|
||||
return;
|
||||
|
||||
int targetIndex = index - mCurIndex + 1;
|
||||
@@ -715,14 +724,13 @@ public class GalleryView extends GLView
|
||||
* You'd better resetSizePosition(PRE_TARGET_INDEX) before
|
||||
* @return
|
||||
*/
|
||||
private boolean goToPrePage() {
|
||||
private synchronized boolean goToPrePage() {
|
||||
if (isFirstPage())
|
||||
return false;
|
||||
|
||||
ShowItem showItem;
|
||||
showItem = showItems[NEXT_TARGET_INDEX];
|
||||
if (showItem != null)
|
||||
showItem.recycle();
|
||||
if (showItems[NEXT_TARGET_INDEX] != null)
|
||||
showItems[NEXT_TARGET_INDEX].recycle();
|
||||
showItems[NEXT_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
|
||||
showItems[CUR_TARGET_INDEX] = showItems[PRE_TARGET_INDEX];
|
||||
showItems[PRE_TARGET_INDEX] = null;
|
||||
@@ -741,8 +749,8 @@ public class GalleryView extends GLView
|
||||
setState();
|
||||
loadImage(mCurIndex-1);
|
||||
|
||||
if (mOnScrollPageListener != null) {
|
||||
mOnScrollPageListener.onScrollPage(mCurIndex);
|
||||
if (mGalleryViewListener != null) {
|
||||
mGalleryViewListener.onPageChanged(mCurIndex);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
@@ -753,14 +761,13 @@ public class GalleryView extends GLView
|
||||
* You'd better resetSizePosition(NEXT_TARGET_INDEX) before
|
||||
* @return
|
||||
*/
|
||||
private boolean goToNextPage() {
|
||||
private synchronized boolean goToNextPage() {
|
||||
if (isLastPage())
|
||||
return false;
|
||||
|
||||
ShowItem showItem;
|
||||
showItem = showItems[PRE_TARGET_INDEX];
|
||||
if (showItem != null)
|
||||
showItem.recycle();
|
||||
if (showItems[PRE_TARGET_INDEX] != null)
|
||||
showItems[PRE_TARGET_INDEX].recycle();
|
||||
showItems[PRE_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
|
||||
showItems[CUR_TARGET_INDEX] = showItems[NEXT_TARGET_INDEX];
|
||||
showItems[NEXT_TARGET_INDEX] = null;
|
||||
@@ -778,20 +785,58 @@ public class GalleryView extends GLView
|
||||
setState();
|
||||
loadImage(mCurIndex+1);
|
||||
|
||||
if (mOnScrollPageListener != null) {
|
||||
mOnScrollPageListener.onScrollPage(mCurIndex);
|
||||
if (mGalleryViewListener != null) {
|
||||
mGalleryViewListener.onPageChanged(mCurIndex);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean goToPage(int index) {
|
||||
if (!isIndexValid(index))
|
||||
return false;
|
||||
|
||||
if (index == mCurIndex) {
|
||||
return true;
|
||||
} else if (index == mCurIndex - 1) {
|
||||
return goToPrePage();
|
||||
} else if (index == mCurIndex + 1) {
|
||||
return goToNextPage();
|
||||
} else {
|
||||
// Free
|
||||
if (showItems[PRE_TARGET_INDEX] != null)
|
||||
showItems[PRE_TARGET_INDEX].recycle();
|
||||
if (showItems[CUR_TARGET_INDEX] != null)
|
||||
showItems[CUR_TARGET_INDEX].recycle();
|
||||
if (showItems[NEXT_TARGET_INDEX] != null)
|
||||
showItems[NEXT_TARGET_INDEX].recycle();
|
||||
|
||||
mCurIndex = index;
|
||||
setState();
|
||||
loadImage(mCurIndex-1);
|
||||
loadImage(mCurIndex);
|
||||
loadImage(mCurIndex+1);
|
||||
|
||||
if (mGalleryViewListener != null) {
|
||||
mGalleryViewListener.onPageChanged(mCurIndex);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onTouch(MotionEvent event) {
|
||||
mGestureRecognizer.onTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setTouchLocked(boolean locked) {
|
||||
|
||||
}
|
||||
|
||||
private class MyGestureListener implements GestureRecognizer.Listener {
|
||||
|
||||
private boolean isScale = false;
|
||||
@@ -880,21 +925,28 @@ public class GalleryView extends GLView
|
||||
if (Utils.isInArea(leftArea, (int)x, (int)y)) {
|
||||
// TODO goto bottom first the to pre page
|
||||
resetSizePosition(PRE_TARGET_INDEX);
|
||||
if (!goToPrePage() && mOnEdgeListener != null)
|
||||
mOnEdgeListener.onFirstPageEdge();
|
||||
if (!goToPrePage()) {
|
||||
// Get to first page
|
||||
MaterialToast.showToast("第一页"); // TODO
|
||||
}
|
||||
} else if (Utils.isInArea(topArea, (int)x, (int)y)) {
|
||||
zoom(true);
|
||||
} else if (Utils.isInArea(rightArea, (int)x, (int)y)) {
|
||||
// TODO goto bottom first the to pre page
|
||||
resetSizePosition(NEXT_TARGET_INDEX);
|
||||
if (!goToNextPage() && mOnEdgeListener != null)
|
||||
mOnEdgeListener.onLastPageEdge();
|
||||
if (!goToNextPage()) {
|
||||
// Get to last page
|
||||
MaterialToast.showToast(isEnsureSize ? "最后一页" : "等待获取后续页面"); // TODO
|
||||
}
|
||||
} else if (Utils.isInArea(bottomArea, (int)x, (int)y)) {
|
||||
zoom(false);
|
||||
} else if (Utils.isInArea(centerArea, (int)x, (int)y)) {
|
||||
mShowTapArea = true;
|
||||
mShowTapAreaTurn = true;
|
||||
invalidate();
|
||||
if (mGalleryViewListener != null)
|
||||
mGalleryViewListener.onTapCenter();
|
||||
|
||||
//mShowTapArea = true;
|
||||
//mShowTapAreaTurn = true;
|
||||
//invalidate();
|
||||
} else {
|
||||
// Can't catch tap
|
||||
}
|
||||
@@ -914,7 +966,7 @@ public class GalleryView extends GLView
|
||||
return;
|
||||
|
||||
Utils.vibrator(mContext, 100);
|
||||
mImageSet.setTargetIndex(mCurIndex);
|
||||
mImageSet.setStartIndex(mCurIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -960,11 +1012,6 @@ public class GalleryView extends GLView
|
||||
resetSizePosition(PRE_TARGET_INDEX);
|
||||
} else {
|
||||
changePage = false;
|
||||
if (mShowEdegTip) { // First page
|
||||
mShowEdegTip = false;
|
||||
if (mOnEdgeListener != null)
|
||||
mOnEdgeListener.onFirstPageEdge();
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Go to righ
|
||||
@@ -974,11 +1021,6 @@ public class GalleryView extends GLView
|
||||
resetSizePosition(NEXT_TARGET_INDEX);
|
||||
} else {
|
||||
changePage = false;
|
||||
if (mShowEdegTip) { // last page
|
||||
mShowEdegTip = false;
|
||||
if (mOnEdgeListener != null)
|
||||
mOnEdgeListener.onLastPageEdge();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1054,6 +1096,11 @@ public class GalleryView extends GLView
|
||||
mReturnTimeRunner.start();
|
||||
}
|
||||
break;
|
||||
case SCROLL_NONE:
|
||||
if (isFirstPage())
|
||||
MaterialToast.showToast("第一页");
|
||||
else if (isLastPage())
|
||||
MaterialToast.showToast(isEnsureSize ? "最后一页" : "等待获取后续页面"); // TODO
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1123,7 +1170,6 @@ public class GalleryView extends GLView
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
mShowEdegTip = true;
|
||||
|
||||
|
||||
// if use onSingleTapUp, use below
|
||||
|
||||
@@ -127,6 +127,10 @@ public class ImageSet implements ExDownloader.ListenerForImageSet {
|
||||
mExDownloader.setTargetIndex(index);
|
||||
}
|
||||
|
||||
public void setStartIndex(int startIndex) {
|
||||
mExDownloader.setStartIndex(startIndex);
|
||||
}
|
||||
|
||||
public Object getImage(int index) {
|
||||
// Try to get file name
|
||||
String filename = mImageFilenameArray.get(index);
|
||||
|
||||
@@ -935,9 +935,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
int target = texture.getTarget();
|
||||
GLES20.glBindTexture(target, texture.getId());
|
||||
checkError();
|
||||
// TODO Maybe multi-thread error
|
||||
if (!bitmap.isRecycled())
|
||||
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
|
||||
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,6 +42,10 @@ public class GalleryUtils {
|
||||
sCurrentThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
public static boolean isRenderThread() {
|
||||
return sCurrentThread == Thread.currentThread();
|
||||
}
|
||||
|
||||
public static void assertNotInRenderThread() {
|
||||
if (!sWarned) {
|
||||
if (Thread.currentThread() == sCurrentThread) {
|
||||
|
||||
@@ -16,33 +16,65 @@
|
||||
|
||||
package com.hippo.ehviewer.ui;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.AppHandler;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.gallery.GalleryView;
|
||||
import com.hippo.ehviewer.gallery.data.ImageSet;
|
||||
import com.hippo.ehviewer.gallery.ui.GLRootView;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Constants;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
import com.hippo.ehviewer.widget.AlertButton;
|
||||
import com.hippo.ehviewer.widget.DialogBuilder;
|
||||
|
||||
public class GalleryActivity extends AbstractActivity {
|
||||
public class GalleryActivity extends AbstractActivity
|
||||
implements GalleryView.GalleryViewListener, SeekBar.OnSeekBarChangeListener {
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = GalleryActivity.class.getSimpleName();
|
||||
|
||||
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
|
||||
private static final int ACTION_BAR_HEIGHT = 48;
|
||||
private static final int NAV_BAR_HEIGHT = 48;
|
||||
|
||||
public final static String KEY_GID = "gid";
|
||||
public final static String KEY_TOKEN = "token";
|
||||
public final static String KEY_TITLE = "title";
|
||||
public final static String KEY_START_INDEX = "start_index";
|
||||
|
||||
private RelativeLayout mainView;
|
||||
private View mainView;
|
||||
private View mActionBar;
|
||||
private View mNavBar;
|
||||
private TextView mTitleView;
|
||||
private TextView mSeekerBubble;
|
||||
|
||||
/** It store index, start from 0, so max is samller than size **/
|
||||
private SeekBar mPageSeeker;
|
||||
private ImageSet mImageSet;
|
||||
|
||||
private GalleryView mGalleryView;
|
||||
|
||||
private int mGid;
|
||||
private String mTitle;
|
||||
|
||||
private ValueAnimator mShowAnimator;
|
||||
private ValueAnimator mHideAnimator;
|
||||
private long mHideTaskTimeStamp;
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int paddingTop, int paddingBottom) {
|
||||
// Empty
|
||||
@@ -63,17 +95,83 @@ public class GalleryActivity extends AbstractActivity {
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}
|
||||
}
|
||||
|
||||
private void initAnimator() {
|
||||
mShowAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
|
||||
mShowAnimator.setDuration(Constants.ANIMATE_TIME);
|
||||
mShowAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
|
||||
mShowAnimator.addUpdateListener(new AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float value = (Float)animation.getAnimatedValue();
|
||||
mActionBar.setY(- value * Ui.dp2pix(ACTION_BAR_HEIGHT));
|
||||
mNavBar.setY(mainView.getHeight() - (1.0f - value) * Ui.dp2pix(NAV_BAR_HEIGHT));
|
||||
mActionBar.requestLayout();
|
||||
mNavBar.requestLayout();
|
||||
}
|
||||
});
|
||||
mShowAnimator.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mActionBar.setVisibility(View.VISIBLE);
|
||||
mNavBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {}
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {}
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {}
|
||||
});
|
||||
|
||||
mHideAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
|
||||
mHideAnimator.setDuration(Constants.ANIMATE_TIME);
|
||||
mHideAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
|
||||
mHideAnimator.addUpdateListener(new AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float value = (Float)animation.getAnimatedValue();
|
||||
mActionBar.setY(- value * Ui.dp2pix(ACTION_BAR_HEIGHT));
|
||||
mNavBar.setY(mainView.getHeight() - (1.0f - value) * Ui.dp2pix(NAV_BAR_HEIGHT));
|
||||
mActionBar.requestLayout();
|
||||
mNavBar.requestLayout();
|
||||
}
|
||||
});
|
||||
mHideAnimator.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mActionBar.setVisibility(View.VISIBLE);
|
||||
mNavBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mActionBar.setVisibility(View.GONE);
|
||||
mNavBar.setVisibility(View.GONE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {}
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.gl_root_group);
|
||||
|
||||
mainView = findViewById(R.id.main);
|
||||
mActionBar = findViewById(R.id.action_bar);
|
||||
mNavBar = findViewById(R.id.nav_bar);
|
||||
mTitleView = (TextView)findViewById(R.id.title);
|
||||
mPageSeeker = (SeekBar)findViewById(R.id.page_seeker);
|
||||
mSeekerBubble = (TextView)findViewById(R.id.seeker_bubble);
|
||||
|
||||
// FullScreen
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
mainView = (RelativeLayout)findViewById(R.id.main);
|
||||
mainView.setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
@@ -84,12 +182,12 @@ public class GalleryActivity extends AbstractActivity {
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
int gid = intent.getIntExtra(KEY_GID, -1);
|
||||
mGid = intent.getIntExtra(KEY_GID, -1);
|
||||
String token = intent.getStringExtra(KEY_TOKEN);
|
||||
String title = intent.getStringExtra(KEY_TITLE);
|
||||
mTitle = intent.getStringExtra(KEY_TITLE);
|
||||
int startIndex = intent.getIntExtra(KEY_START_INDEX, 0);
|
||||
|
||||
if (gid == -1 || token == null || title == null) {
|
||||
if (mGid == -1 || token == null || mTitle == null) {
|
||||
new DialogBuilder(this).setTitle(R.string.error)
|
||||
.setMessage("数据错误!").setPositiveButton("关闭",
|
||||
new View.OnClickListener() {
|
||||
@@ -100,17 +198,128 @@ public class GalleryActivity extends AbstractActivity {
|
||||
}
|
||||
}).create().show();
|
||||
} else {
|
||||
mImageSet = new ImageSet(gid, token, title, startIndex);
|
||||
GalleryView isv = new GalleryView(getApplicationContext(), mImageSet, startIndex);
|
||||
mImageSet = new ImageSet(mGid, token, mTitle, startIndex);
|
||||
mGalleryView = new GalleryView(getApplicationContext(), mImageSet, startIndex);
|
||||
GLRootView glrv= (GLRootView)findViewById(R.id.gl_root_view);
|
||||
glrv.setContentPane(isv);
|
||||
glrv.setContentPane(mGalleryView);
|
||||
|
||||
initAnimator();
|
||||
|
||||
// Set listener
|
||||
mGalleryView.setGalleryViewListener(this);
|
||||
mPageSeeker.setOnSeekBarChangeListener(this);
|
||||
|
||||
updateTitle();
|
||||
mPageSeeker.setMax(mGalleryView.getSize() - 1);
|
||||
mPageSeeker.setProgress(startIndex);
|
||||
|
||||
// Show first tip
|
||||
if (Config.getGalleryFirst()) {
|
||||
Config.setGalleryFirst(false);
|
||||
new DialogBuilder(this).setTitle("提示")
|
||||
.setMessage("长按屏幕,可使下载从当前页面开始") // TODO
|
||||
.setSimpleNegativeButton().create().show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
sb.append(mGalleryView.getCurIndex() + 1).append("/")
|
||||
.append(mGalleryView.getSize())
|
||||
.append(" - ").append(mGid);
|
||||
mTitleView.setText(sb.toString());
|
||||
}
|
||||
|
||||
private void startHideTask() {
|
||||
mHideTaskTimeStamp = System.currentTimeMillis();
|
||||
AppHandler.getInstance().postDelayed(new HideTask(mHideTaskTimeStamp), 2000);
|
||||
}
|
||||
|
||||
private void cancelHideTask() {
|
||||
mHideTaskTimeStamp = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mImageSet.free();
|
||||
if (mImageSet != null)
|
||||
mImageSet.free();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTapCenter() {
|
||||
if (mShowAnimator.isRunning() || mHideAnimator.isRunning())
|
||||
return;
|
||||
|
||||
if (mActionBar.getVisibility() == View.GONE) {
|
||||
mShowAnimator.start();
|
||||
startHideTask();
|
||||
} else if (mActionBar.getVisibility() == View.VISIBLE) {
|
||||
mHideAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
// The task to hide actionbar
|
||||
private class HideTask implements Runnable {
|
||||
private final long mTimeStamp;
|
||||
|
||||
public HideTask(long timeStamp) {
|
||||
mTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mTimeStamp == mHideTaskTimeStamp &&!mShowAnimator.isRunning() &&
|
||||
!mHideAnimator.isRunning() && mActionBar.getVisibility() == View.VISIBLE)
|
||||
mHideAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageChanged(int index) {
|
||||
updateTitle();
|
||||
mPageSeeker.setProgress(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeUpdate(int size) {
|
||||
updateTitle();
|
||||
mPageSeeker.setMax(size - 1);
|
||||
}
|
||||
|
||||
private void showSeekBubble() {
|
||||
mSeekerBubble.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideSeekBubble() {
|
||||
mSeekerBubble.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void updateSeekBubble(int progress, int max, int thumbOffset) {
|
||||
thumbOffset = thumbOffset + Ui.dp2pix(12);
|
||||
int x = (mainView.getWidth() - 2 * thumbOffset) * progress / max + thumbOffset;
|
||||
mSeekerBubble.setX(x);
|
||||
mSeekerBubble.setText(String.valueOf(progress + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||
boolean fromUser) {
|
||||
updateSeekBubble(progress, seekBar.getMax(), seekBar.getThumbOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
cancelHideTask();
|
||||
showSeekBubble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
startHideTask();
|
||||
hideSeekBubble();
|
||||
mGalleryView.goToPage(seekBar.getProgress());
|
||||
}
|
||||
}
|
||||
@@ -268,6 +268,8 @@ public class GalleryListActivity extends AbstractGalleryActivity
|
||||
modeSpinner.setSelection(Config.getMode());
|
||||
final Spinner apiModeSpinner = (Spinner)customLayout.findViewById(R.id.api_mode_list);
|
||||
apiModeSpinner.setSelection(Config.getApiMode());
|
||||
final Spinner lofiResolutionSpinner = (Spinner)customLayout.findViewById(R.id.lofi_resolution_list);
|
||||
lofiResolutionSpinner.setSelection(Config.getLofiResolution() - 1);
|
||||
return db.setSimpleNegativeButton()
|
||||
.setPositiveButton(android.R.string.ok, new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -275,6 +277,7 @@ public class GalleryListActivity extends AbstractGalleryActivity
|
||||
((AlertButton)v).dialog.dismiss();
|
||||
Config.setMode(modeSpinner.getSelectedItemPosition());
|
||||
Config.setApiMode(apiModeSpinner.getSelectedItemPosition());
|
||||
Config.setLofiResolution(lofiResolutionSpinner.getSelectedItemPosition() + 1);
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
@@ -354,6 +354,21 @@ public class Config {
|
||||
KEY_PREVIEW_COLUMNS_LANDSCAPE, DEFAULT_PREVIEW_COLUMNS_LANDSCAPE);
|
||||
}
|
||||
|
||||
/****** GalleryActivity ******/
|
||||
|
||||
private static final String KEY_GALLERY_FIRST = "gallery_first";
|
||||
private static final boolean DEFAULT_GALLERY_FIRST = true;
|
||||
|
||||
public static boolean getGalleryFirst() {
|
||||
return mConfigPre.getBoolean(KEY_GALLERY_FIRST, DEFAULT_GALLERY_FIRST);
|
||||
}
|
||||
|
||||
public static void setGalleryFirst(boolean galleryFirst) {
|
||||
mConfigPre.edit().putBoolean(KEY_GALLERY_FIRST, galleryFirst).apply();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/****** Mode an API Mode ******/
|
||||
|
||||
private static final String KEY_MODE = "mode";
|
||||
@@ -362,6 +377,9 @@ public class Config {
|
||||
private static final String KEY_API_MODE = "api_mode";
|
||||
private static final int DEFAULT_API_MODE = EhClient.MODE_G;
|
||||
|
||||
private static final String KEY_LOFI_RESOLUTION = "lofi_resolution";
|
||||
private static final int DEFAULT_LOFI_RESOLUTION = 1;
|
||||
|
||||
public static int getMode() {
|
||||
return mConfigPre.getInt(KEY_MODE, DEFAULT_MODE);
|
||||
}
|
||||
@@ -378,6 +396,14 @@ public class Config {
|
||||
mConfigPre.edit().putInt(KEY_API_MODE, apiMode).apply();
|
||||
}
|
||||
|
||||
public static int getLofiResolution() {
|
||||
return mConfigPre.getInt(KEY_LOFI_RESOLUTION, DEFAULT_LOFI_RESOLUTION);
|
||||
}
|
||||
|
||||
public static void setLofiResolution(int lofiResolution) {
|
||||
mConfigPre.edit().putInt(KEY_LOFI_RESOLUTION, lofiResolution).apply();
|
||||
}
|
||||
|
||||
/****** Eh Config ******/
|
||||
|
||||
private static final String KEY_DEFAULT_CAT = "default_cat";
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* 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 com.hippo.ehviewer.util;
|
||||
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
public final class DateUtils {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static boolean hasSeconds(CharSequence inFormat) {
|
||||
return hasDesignator(inFormat, DateFormat.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static boolean hasDesignator(CharSequence inFormat, char designator) {
|
||||
if (inFormat == null) return false;
|
||||
|
||||
final int length = inFormat.length();
|
||||
|
||||
int c;
|
||||
int count;
|
||||
|
||||
for (int i = 0; i < length; i += count) {
|
||||
count = 1;
|
||||
c = inFormat.charAt(i);
|
||||
|
||||
if (c == DateFormat.QUOTE) {
|
||||
count = skipQuotedText(inFormat, i, length);
|
||||
} else if (c == designator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static int skipQuotedText(CharSequence s, int i, int len) {
|
||||
if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
int count = 1;
|
||||
// skip leading quote
|
||||
i++;
|
||||
|
||||
while (i < len) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == DateFormat.QUOTE) {
|
||||
count++;
|
||||
// QUOTEQUOTE -> QUOTE
|
||||
if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) {
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* 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 com.hippo.ehviewer.widget;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewDebug.ExportedProperty;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.util.DateUtils;
|
||||
|
||||
public class TextClock extends TextView {
|
||||
|
||||
public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a";
|
||||
public static final CharSequence DEFAULT_FORMAT_24_HOUR = "HH:mm";
|
||||
|
||||
private CharSequence mFormat12;
|
||||
private CharSequence mFormat24;
|
||||
|
||||
@ExportedProperty
|
||||
private CharSequence mFormat;
|
||||
@ExportedProperty
|
||||
private boolean mHasSeconds;
|
||||
|
||||
private boolean mAttached;
|
||||
|
||||
private Calendar mTime;
|
||||
private String mTimeZone;
|
||||
|
||||
private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
|
||||
final String timeZone = intent.getStringExtra("time-zone");
|
||||
createTime(timeZone);
|
||||
}
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mTicker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onTimeChanged();
|
||||
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long next = now + (1000 - now % 1000);
|
||||
|
||||
getHandler().postAtTime(mTicker, next);
|
||||
}
|
||||
};
|
||||
|
||||
public TextClock(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextClock(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TextClock(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
mFormat12 = DEFAULT_FORMAT_12_HOUR;
|
||||
mFormat24 = DEFAULT_FORMAT_24_HOUR;
|
||||
mTimeZone = TimeZone.getDefault().getID();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
createTime(mTimeZone);
|
||||
// Wait until onAttachedToWindow() to handle the ticker
|
||||
chooseFormat(false);
|
||||
}
|
||||
|
||||
private void createTime(String timeZone) {
|
||||
if (timeZone != null) {
|
||||
mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
|
||||
} else {
|
||||
mTime = Calendar.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence getFormat12Hour() {
|
||||
return mFormat12;
|
||||
}
|
||||
|
||||
public void setFormat12Hour(CharSequence format) {
|
||||
mFormat12 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public CharSequence getFormat24Hour() {
|
||||
return mFormat24;
|
||||
}
|
||||
|
||||
public void setFormat24Hour(CharSequence format) {
|
||||
mFormat24 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public boolean is24HourModeEnabled() {
|
||||
return DateFormat.is24HourFormat(getContext());
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return mTimeZone;
|
||||
}
|
||||
|
||||
public void setTimeZone(String timeZone) {
|
||||
mTimeZone = timeZone;
|
||||
|
||||
createTime(timeZone);
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
|
||||
* depending on whether the user has selected 24-hour format.
|
||||
*
|
||||
* Calling this method does not schedule or unschedule the time ticker.
|
||||
*/
|
||||
private void chooseFormat() {
|
||||
chooseFormat(true);
|
||||
}
|
||||
|
||||
public CharSequence getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
|
||||
* depending on whether the user has selected 24-hour format.
|
||||
*
|
||||
* @param handleTicker true if calling this method should schedule/unschedule the
|
||||
* time ticker, false otherwise
|
||||
*/
|
||||
private void chooseFormat(boolean handleTicker) {
|
||||
final boolean format24Requested = is24HourModeEnabled();
|
||||
|
||||
if (format24Requested) {
|
||||
mFormat = mFormat24;
|
||||
} else {
|
||||
mFormat = mFormat12;
|
||||
}
|
||||
|
||||
boolean hadSeconds = mHasSeconds;
|
||||
mHasSeconds = DateUtils.hasSeconds(mFormat);
|
||||
|
||||
if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
|
||||
if (hadSeconds) getHandler().removeCallbacks(mTicker);
|
||||
else mTicker.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (!mAttached) {
|
||||
mAttached = true;
|
||||
|
||||
registerReceiver();
|
||||
registerObserver();
|
||||
|
||||
createTime(mTimeZone);
|
||||
|
||||
if (mHasSeconds) {
|
||||
mTicker.run();
|
||||
} else {
|
||||
onTimeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAttached) {
|
||||
unregisterReceiver();
|
||||
unregisterObserver();
|
||||
|
||||
getHandler().removeCallbacks(mTicker);
|
||||
|
||||
mAttached = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReceiver() {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
|
||||
filter.addAction(Intent.ACTION_TIME_TICK);
|
||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
|
||||
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
|
||||
}
|
||||
|
||||
private void registerObserver() {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
|
||||
}
|
||||
|
||||
private void unregisterReceiver() {
|
||||
getContext().unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
private void unregisterObserver() {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.unregisterContentObserver(mFormatChangeObserver);
|
||||
}
|
||||
|
||||
private void onTimeChanged() {
|
||||
mTime.setTimeInMillis(System.currentTimeMillis());
|
||||
setText(DateFormat.format(mFormat, mTime));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user