diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 6731888f3..7ff600ab7 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -270,16 +270,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan mPage = savedInstanceState.getInt(KEY_PAGE, -1); mCurrentIndex = savedInstanceState.getInt(KEY_CURRENT_INDEX); buildProvider(); - } ActivityResultLauncher requestStoragePermissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - result -> { - if (result && mSavingPage != -1) { - saveImage(mSavingPage); - } else { - Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show(); - } - mSavingPage = -1; - }); + } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { @@ -292,7 +283,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan } outState.putInt(KEY_PAGE, mPage); outState.putInt(KEY_CURRENT_INDEX, mCurrentIndex); - } + } ActivityResultLauncher requestStoragePermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + result -> { + if (result && mSavingPage != -1) { + saveImage(mSavingPage); + } else { + Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show(); + } + mSavingPage = -1; + }); @Override protected void attachBaseContext(@NonNull Context newBase) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/BasePreferenceFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/BasePreferenceFragment.java index 29862158e..5b74c04a0 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/fragment/BasePreferenceFragment.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/BasePreferenceFragment.java @@ -1,13 +1,10 @@ package com.hippo.ehviewer.ui.fragment; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.preference.Preference; -import androidx.recyclerview.widget.RecyclerView; import com.hippo.ehviewer.ui.SettingsActivity; import com.takisoft.preferencex.PreferenceFragmentCompat; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java index c483fabc3..72da9719a 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java @@ -30,7 +30,6 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.util.SparseBooleanArray; -import android.util.TypedValue; import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java index 9cf756af7..22b6bc2f5 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java @@ -34,7 +34,6 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewAnimationUtils; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java index 6aa259d53..4ecd77712 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java @@ -24,7 +24,6 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; -import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; diff --git a/app/src/main/java/com/hippo/glgallery/DownUpDetector.java b/app/src/main/java/com/hippo/glgallery/DownUpDetector.java index b54fb0a5d..56f74d7cd 100644 --- a/app/src/main/java/com/hippo/glgallery/DownUpDetector.java +++ b/app/src/main/java/com/hippo/glgallery/DownUpDetector.java @@ -19,16 +19,8 @@ package com.hippo.glgallery; import android.view.MotionEvent; class DownUpDetector { - public interface DownUpListener { - void onDown(MotionEvent e); - void onUp(MotionEvent e); - void onPointerDown(MotionEvent e); - void onPointerUp(MotionEvent e); - } - - private boolean mStillDown; private final DownUpListener mListener; - + private boolean mStillDown; public DownUpDetector(DownUpListener listener) { mListener = listener; } @@ -64,4 +56,14 @@ class DownUpDetector { public boolean isDown() { return mStillDown; } + + public interface DownUpListener { + void onDown(MotionEvent e); + + void onUp(MotionEvent e); + + void onPointerDown(MotionEvent e); + + void onPointerUp(MotionEvent e); + } } diff --git a/app/src/main/java/com/hippo/glgallery/Fling.java b/app/src/main/java/com/hippo/glgallery/Fling.java index 01bfc2db0..d6d57a65f 100644 --- a/app/src/main/java/com/hippo/glgallery/Fling.java +++ b/app/src/main/java/com/hippo/glgallery/Fling.java @@ -35,10 +35,25 @@ abstract class Fling extends Animation { private static final int NB_SAMPLES = 100; private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1]; + private static final Interpolator FLING_INTERPOLATOR = new Interpolator() { + @Override + public float getInterpolation(float input) { + final int index = (int) (NB_SAMPLES * input); + float distanceCoef = 1.f; + float velocityCoef; + if (index < NB_SAMPLES) { + final float t_inf = (float) index / NB_SAMPLES; + final float t_sup = (float) (index + 1) / NB_SAMPLES; + final float d_inf = SPLINE_POSITION[index]; + final float d_sup = SPLINE_POSITION[index + 1]; + velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); + distanceCoef = d_inf + (input - t_inf) * velocityCoef; + } + return distanceCoef; + } + }; private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; - private final float mPhysicalCoeff; - static { float x_min = 0.0f; float y_min = 0.0f; @@ -72,23 +87,7 @@ abstract class Fling extends Animation { SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; } - private static final Interpolator FLING_INTERPOLATOR = new Interpolator() { - @Override - public float getInterpolation(float input) { - final int index = (int) (NB_SAMPLES * input); - float distanceCoef = 1.f; - float velocityCoef; - if (index < NB_SAMPLES) { - final float t_inf = (float) index / NB_SAMPLES; - final float t_sup = (float) (index + 1) / NB_SAMPLES; - final float d_inf = SPLINE_POSITION[index]; - final float d_sup = SPLINE_POSITION[index + 1]; - velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); - distanceCoef = d_inf + (input - t_inf) * velocityCoef; - } - return distanceCoef; - } - }; + private final float mPhysicalCoeff; public Fling(Context context) { final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; @@ -117,8 +116,8 @@ abstract class Fling extends Animation { } /** - * Modifies mDuration to the duration it takes to get from start to newFinal using the - * spline interpolation. The previous duration was needed to get to oldFinal. + * Modifies mDuration to the duration it takes to get from start to newFinal using the + * spline interpolation. The previous duration was needed to get to oldFinal. **/ protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) { final int oldDistance = oldFinal - start; diff --git a/app/src/main/java/com/hippo/glgallery/GalleryPageView.java b/app/src/main/java/com/hippo/glgallery/GalleryPageView.java index 2b9376fce..6170195df 100644 --- a/app/src/main/java/com/hippo/glgallery/GalleryPageView.java +++ b/app/src/main/java/com/hippo/glgallery/GalleryPageView.java @@ -45,8 +45,8 @@ public class GalleryPageView extends GLFrameLayout { private int mIndex = INVALID_INDEX; public GalleryPageView(ImageMovableTextTexture pageTextTexture, - int progressColor, int progressBgColor, int progressSize, - int minHeight, int infoInterval) { + int progressColor, int progressBgColor, int progressSize, + int minHeight, int infoInterval) { // Add image mImage = new ImageView(); GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT, diff --git a/app/src/main/java/com/hippo/glgallery/GalleryProvider.java b/app/src/main/java/com/hippo/glgallery/GalleryProvider.java index c203a9ff2..adc2cbc71 100644 --- a/app/src/main/java/com/hippo/glgallery/GalleryProvider.java +++ b/app/src/main/java/com/hippo/glgallery/GalleryProvider.java @@ -38,11 +38,9 @@ public abstract class GalleryProvider { public static final int STATE_ERROR = -2; private final ConcurrentPool mNotifyTaskPool = new ConcurrentPool<>(5); + private final ImageCache mImageCache = new ImageCache(); private volatile Listener mListener; private volatile GLRoot mGLRoot; - - private final ImageCache mImageCache = new ImageCache(); - private boolean mStarted = false; @UiThread @@ -67,8 +65,8 @@ public abstract class GalleryProvider { /** * @return {@link #STATE_WAIT} for wait, - * {@link #STATE_ERROR} for error, {@link #getError()} to get error message, - * 0 for empty + * {@link #STATE_ERROR} for error, {@link #getError()} to get error message, + * 0 for empty */ public abstract int size(); @@ -154,28 +152,36 @@ public abstract class GalleryProvider { glRoot.addOnGLIdleListener(task); } - private static class NotifyTask implements GLRoot.OnGLIdleListener { + public interface Listener { - @IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED}) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} + void onDataChanged(); + + void onPageWait(int index); + + void onPagePercent(int index, float percent); + + void onPageSucceed(int index, ImageWrapper image); + + void onPageFailed(int index, String error); + + void onDataChanged(int index); + } + + private static class NotifyTask implements GLRoot.OnGLIdleListener { public static final int TYPE_DATA_CHANGED = 0; public static final int TYPE_WAIT = 1; public static final int TYPE_PERCENT = 2; public static final int TYPE_SUCCEED = 3; public static final int TYPE_FAILED = 4; - private final Listener mListener; private final ConcurrentPool mPool; - @Type private int mType; private int mIndex; private float mPercent; private ImageWrapper mImage; private String mError; - public NotifyTask(Listener listener, ConcurrentPool pool) { mListener = listener; mPool = pool; @@ -221,6 +227,11 @@ public abstract class GalleryProvider { return false; } + + @IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } } private static class ImageCache extends LruCache { @@ -254,19 +265,4 @@ public abstract class GalleryProvider { } } } - - public interface Listener { - - void onDataChanged(); - - void onPageWait(int index); - - void onPagePercent(int index, float percent); - - void onPageSucceed(int index, ImageWrapper image); - - void onPageFailed(int index, String error); - - void onDataChanged(int index); - } } diff --git a/app/src/main/java/com/hippo/glgallery/GalleryView.java b/app/src/main/java/com/hippo/glgallery/GalleryView.java index b00ce5864..217876ade 100644 --- a/app/src/main/java/com/hippo/glgallery/GalleryView.java +++ b/app/src/main/java/com/hippo/glgallery/GalleryView.java @@ -41,6 +41,7 @@ import com.hippo.glview.widget.GLProgressView; import com.hippo.glview.widget.GLTextureView; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.Pool; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -49,40 +50,23 @@ import java.util.concurrent.atomic.AtomicInteger; public final class GalleryView extends GLView implements GestureRecognizer.Listener { - @IntDef({LAYOUT_LEFT_TO_RIGHT, LAYOUT_RIGHT_TO_LEFT, LAYOUT_TOP_TO_BOTTOM}) - @Retention(RetentionPolicy.SOURCE) - public @interface LayoutMode {} - - @IntDef({SCALE_ORIGIN, SCALE_FIT_WIDTH, SCALE_FIT_HEIGHT, SCALE_FIT, SCALE_FIXED}) - @Retention(RetentionPolicy.SOURCE) - public @interface ScaleMode {} - - @IntDef({START_POSITION_TOP_LEFT, START_POSITION_TOP_RIGHT, START_POSITION_BOTTOM_LEFT, - START_POSITION_BOTTOM_RIGHT, START_POSITION_CENTER}) - @Retention(RetentionPolicy.SOURCE) - public @interface StartPosition {} - public static final int LAYOUT_LEFT_TO_RIGHT = 0; public static final int LAYOUT_RIGHT_TO_LEFT = 1; public static final int LAYOUT_TOP_TO_BOTTOM = 2; - public static final int SCALE_ORIGIN = ImageView.SCALE_ORIGIN; public static final int SCALE_FIT_WIDTH = ImageView.SCALE_FIT_WIDTH; public static final int SCALE_FIT_HEIGHT = ImageView.SCALE_FIT_HEIGHT; public static final int SCALE_FIT = ImageView.SCALE_FIT; public static final int SCALE_FIXED = ImageView.SCALE_FIXED; - public static final int START_POSITION_TOP_LEFT = ImageView.START_POSITION_TOP_LEFT; public static final int START_POSITION_TOP_RIGHT = ImageView.START_POSITION_TOP_RIGHT; public static final int START_POSITION_BOTTOM_LEFT = ImageView.START_POSITION_BOTTOM_LEFT; public static final int START_POSITION_BOTTOM_RIGHT = ImageView.START_POSITION_BOTTOM_RIGHT; public static final int START_POSITION_CENTER = ImageView.START_POSITION_CENTER; - private static final float[] LEFT_AREA = {0.0f, 0.0f, 1.0f / 3.0f, 1f}; private static final float[] RIGHT_AREA = {2.0f / 3.0f, 0.0f, 1.0f, 1f}; private static final float[] MENU_AREA = {1.0f / 3.0f, 0.0f, 2.0f / 3.0f, 1.0f / 2.0f}; private static final float[] SLIDER_AREA = {1.0f / 3.0f, 1.0f / 2.0f, 2.0f / 3.0f, 1.0f}; - private static final int METHOD_ON_SINGLE_TAP_UP = 0; private static final int METHOD_ON_SINGLE_TAP_CONFIRMED = 1; private static final int METHOD_ON_DOUBLE_TAP = 2; @@ -106,28 +90,13 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste private static final int METHOD_ON_ATTACH_TO_ROOT = 20; private static final int METHOD_SET_PAGER_INTERVAL = 21; private static final int METHOD_SET_SCROLL_INTERVAL = 22; - private final Context mContext; - private Adapter mAdapter; private final GestureRecognizer mGestureRecognizer; @Nullable private final Listener mListener; - - private ImageMovableTextTexture mPageTextTexture; - - private PagerLayoutManager mPagerLayoutManager; - private ScrollLayoutManager mScrollLayoutManager; - @Nullable - private LayoutManager mLayoutManager; - private final GLEdgeView mEdgeView; private final Pool mGalleryPageViewPool = new Pool<>(5); - private GLProgressView mProgressCache; - private GLTextureView mErrorViewCache; - private final int mBackgroundColor; - private int mPagerInterval; - private int mScrollInterval; private final int mPageMinHeight; private final int mPageInfoInterval; private final int mProgressColor; @@ -137,172 +106,37 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste private final Typeface mPageTextTypeface; private final int mErrorTextSize; private final int mErrorTextColor; - private final String mDefaultErrorString; private final String mEmptyString; - - private boolean mEnableRequestFill = true; - private boolean mRequestFill = false; - private boolean mWillFill = false; - - private boolean mScale = false; - private boolean mScroll = false; - private boolean mFirstScroll = false; - private final Rect mLeftArea = new Rect(); private final Rect mRightArea = new Rect(); private final Rect mMenuArea = new Rect(); private final Rect mSliderArea = new Rect(); - - private int mLayoutMode = LAYOUT_RIGHT_TO_LEFT; - private int mScaleMode = ImageView.SCALE_FIT; - private int mStartPosition = ImageView.START_POSITION_TOP_LEFT; - private int mIndex; - private final List mMethodList = new ArrayList<>(5); private final List mArgsList = new ArrayList<>(5); private final List mMethodListTemp = new ArrayList<>(5); private final List mArgsListTemp = new ArrayList<>(5); - private final AtomicInteger mCurrentIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX); - - public static class Builder { - - private final Context mContext; - private final Adapter mAdapter; - private Listener mListener; - - private int mLayoutMode = LAYOUT_LEFT_TO_RIGHT; - private int mScaleMode = SCALE_FIT; - private int mStartPosition = START_POSITION_TOP_LEFT; - private int mStartPage = 0; - - private int mBackgroundColor = Color.BLACK; - private int mEdgeColor = Color.WHITE; - private int mPagerInterval = 48; - private int mScrollInterval = 24; - private int mPageMinHeight = 256; - private int mPageInfoInterval = 24; - private int mProgressColor = Color.WHITE; - private int mProgressSize = 56; - private int mPageTextColor = Color.WHITE; - private int mPageTextSize = 56; - private Typeface mPageTextTypeface = Typeface.DEFAULT; - private int mErrorTextColor = Color.RED; - private int mErrorTextSize = 24; - private String mDefaultErrorString = "Error"; - private String mEmptyString = "Empty"; - - public Builder(@NonNull Context context, @NonNull Adapter adapter) { - mContext = context; - mAdapter = adapter; - } - - public Builder setListener(Listener listener) { - mListener = listener; - return this; - } - - public Builder setLayoutMode(@LayoutMode int layoutMode) { - mLayoutMode = layoutMode; - return this; - } - - public Builder setScaleMode(@ScaleMode int scaleMode) { - mScaleMode = scaleMode; - return this; - } - - public Builder setStartPosition(@StartPosition int startPosition) { - mStartPosition = startPosition; - return this; - } - - public Builder setStartPage(int startPage) { - mStartPage = startPage; - return this; - } - - public Builder setBackgroundColor(int backgroundColor) { - mBackgroundColor = backgroundColor; - return this; - } - - public Builder setEdgeColor(int edgeColor) { - mEdgeColor = edgeColor; - return this; - } - - public Builder setPagerInterval(int pagerInterval) { - mPagerInterval = pagerInterval; - return this; - } - - public Builder setScrollInterval(int scrollInterval) { - mScrollInterval = scrollInterval; - return this; - } - - public Builder setPageMinHeight(int pageMinHeight) { - mPageMinHeight = pageMinHeight; - return this; - } - - public Builder setPageInfoInterval(int pageInfoInterval) { - mPageInfoInterval = pageInfoInterval; - return this; - } - - public Builder setProgressColor(int progressColor) { - mProgressColor = progressColor; - return this; - } - - public Builder setProgressSize(int progressSize) { - mProgressSize = progressSize; - return this; - } - - public Builder setPageTextColor(int pageTextColor) { - mPageTextColor = pageTextColor; - return this; - } - - public Builder setPageTextSize(int pageTextSize) { - mPageTextSize = pageTextSize; - return this; - } - - public Builder setPageTextTypeface(Typeface pageTextTypeface) { - mPageTextTypeface = pageTextTypeface; - return this; - } - - public Builder setErrorTextColor(int errorTextColor) { - mErrorTextColor = errorTextColor; - return this; - } - - public Builder setErrorTextSize(int errorTextSize) { - mErrorTextSize = errorTextSize; - return this; - } - - public Builder setDefaultErrorString(String defaultErrorString) { - mDefaultErrorString = defaultErrorString; - return this; - } - - public Builder setEmptyString(String emptyString) { - mEmptyString = emptyString; - return this; - } - - public GalleryView build() { - return new GalleryView(this); - } - } - + private Adapter mAdapter; + private ImageMovableTextTexture mPageTextTexture; + private PagerLayoutManager mPagerLayoutManager; + private ScrollLayoutManager mScrollLayoutManager; + @Nullable + private LayoutManager mLayoutManager; + private GLProgressView mProgressCache; + private GLTextureView mErrorViewCache; + private int mPagerInterval; + private int mScrollInterval; + private boolean mEnableRequestFill = true; + private boolean mRequestFill = false; + private boolean mWillFill = false; + private boolean mScale = false; + private boolean mScroll = false; + private boolean mFirstScroll = false; + private int mLayoutMode = LAYOUT_RIGHT_TO_LEFT; + private int mScaleMode = ImageView.SCALE_FIT; + private int mStartPosition = ImageView.START_POSITION_TOP_LEFT; + private int mIndex; private GalleryView(Builder build) { mContext = build.mContext; mAdapter = build.mAdapter; @@ -335,6 +169,43 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste setBackgroundColor(mBackgroundColor); } + @LayoutMode + public static int sanitizeLayoutMode(int layoutMode) { + if (layoutMode != GalleryView.LAYOUT_LEFT_TO_RIGHT && + layoutMode != GalleryView.LAYOUT_RIGHT_TO_LEFT && + layoutMode != GalleryView.LAYOUT_TOP_TO_BOTTOM) { + return GalleryView.LAYOUT_LEFT_TO_RIGHT; + } else { + return layoutMode; + } + } + + @ScaleMode + public static int sanitizeScaleMode(int scaleMode) { + if (scaleMode != GalleryView.SCALE_ORIGIN && + scaleMode != GalleryView.SCALE_FIT_WIDTH && + scaleMode != GalleryView.SCALE_FIT_HEIGHT && + scaleMode != GalleryView.SCALE_FIT && + scaleMode != GalleryView.SCALE_FIXED) { + return GalleryView.SCALE_FIT; + } else { + return scaleMode; + } + } + + @StartPosition + public static int sanitizeStartPosition(int startPosition) { + if (startPosition != GalleryView.START_POSITION_TOP_LEFT && + startPosition != GalleryView.START_POSITION_TOP_RIGHT && + startPosition != GalleryView.START_POSITION_BOTTOM_LEFT && + startPosition != GalleryView.START_POSITION_BOTTOM_RIGHT && + startPosition != GalleryView.START_POSITION_CENTER) { + return GalleryView.START_POSITION_TOP_LEFT; + } else { + return startPosition; + } + } + private void ensurePagerLayoutManager() { if (mPagerLayoutManager == null) { mPagerLayoutManager = new PagerLayoutManager(mContext, this, @@ -436,47 +307,14 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste mEdgeView.onDetachFromRoot(); } - @LayoutMode - public static int sanitizeLayoutMode(int layoutMode) { - if (layoutMode != GalleryView.LAYOUT_LEFT_TO_RIGHT && - layoutMode != GalleryView.LAYOUT_RIGHT_TO_LEFT && - layoutMode != GalleryView.LAYOUT_TOP_TO_BOTTOM) { - return GalleryView.LAYOUT_LEFT_TO_RIGHT; - } else { - return layoutMode; - } - } - - @ScaleMode - public static int sanitizeScaleMode(int scaleMode) { - if (scaleMode != GalleryView.SCALE_ORIGIN && - scaleMode != GalleryView.SCALE_FIT_WIDTH && - scaleMode != GalleryView.SCALE_FIT_HEIGHT && - scaleMode != GalleryView.SCALE_FIT && - scaleMode != GalleryView.SCALE_FIXED) { - return GalleryView.SCALE_FIT; - } else { - return scaleMode; - } - } - - @StartPosition - public static int sanitizeStartPosition(int startPosition) { - if (startPosition != GalleryView.START_POSITION_TOP_LEFT && - startPosition != GalleryView.START_POSITION_TOP_RIGHT && - startPosition != GalleryView.START_POSITION_BOTTOM_LEFT && - startPosition != GalleryView.START_POSITION_BOTTOM_RIGHT && - startPosition != GalleryView.START_POSITION_CENTER) { - return GalleryView.START_POSITION_TOP_LEFT; - } else { - return startPosition; - } - } - public int getLayoutMode() { return mLayoutMode; } + public void setLayoutMode(@LayoutMode int layoutMode) { + postMethod(METHOD_SET_LAYOUT_MODE, layoutMode); + } + public int getCurrentIndex() { return mCurrentIndex.get(); } @@ -531,10 +369,6 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste invalidate(); } - public void setLayoutMode(@LayoutMode int layoutMode) { - postMethod(METHOD_SET_LAYOUT_MODE, layoutMode); - } - public void setCurrentPage(int page) { postMethod(METHOD_SET_CURRENT_PAGE, page); } @@ -669,7 +503,7 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste public void onDataChanged() { GalleryUtils.assertInRenderThread(); - if (mLayoutManager != null){ + if (mLayoutManager != null) { mLayoutManager.onDataChanged(); } } @@ -694,9 +528,9 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste GalleryPageView page = findPageUnder(x, y); if (page != null && - page.getIndex() != GalleryPageView.INVALID_INDEX && - page.isError() && - page.isUnderInfo(x - page.bounds().left, y - page.bounds().top)) { + page.getIndex() != GalleryPageView.INVALID_INDEX && + page.isError() && + page.isUnderInfo(x - page.bounds().left, y - page.bounds().top)) { if (mListener != null) { mListener.onTapErrorText(page.getIndex()); } @@ -1123,6 +957,177 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste mErrorViewCache = errorView; } + @IntDef({LAYOUT_LEFT_TO_RIGHT, LAYOUT_RIGHT_TO_LEFT, LAYOUT_TOP_TO_BOTTOM}) + @Retention(RetentionPolicy.SOURCE) + public @interface LayoutMode { + } + + @IntDef({SCALE_ORIGIN, SCALE_FIT_WIDTH, SCALE_FIT_HEIGHT, SCALE_FIT, SCALE_FIXED}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScaleMode { + } + + @IntDef({START_POSITION_TOP_LEFT, START_POSITION_TOP_RIGHT, START_POSITION_BOTTOM_LEFT, + START_POSITION_BOTTOM_RIGHT, START_POSITION_CENTER}) + @Retention(RetentionPolicy.SOURCE) + public @interface StartPosition { + } + + public interface Listener { + + @RenderThread + void onUpdateCurrentIndex(int index); + + @RenderThread + void onTapSliderArea(); + + @RenderThread + void onTapMenuArea(); + + @RenderThread + void onTapErrorText(int index); + + @RenderThread + void onLongPressPage(int index); + } + + public static class Builder { + + private final Context mContext; + private final Adapter mAdapter; + private Listener mListener; + + private int mLayoutMode = LAYOUT_LEFT_TO_RIGHT; + private int mScaleMode = SCALE_FIT; + private int mStartPosition = START_POSITION_TOP_LEFT; + private int mStartPage = 0; + + private int mBackgroundColor = Color.BLACK; + private int mEdgeColor = Color.WHITE; + private int mPagerInterval = 48; + private int mScrollInterval = 24; + private int mPageMinHeight = 256; + private int mPageInfoInterval = 24; + private int mProgressColor = Color.WHITE; + private int mProgressSize = 56; + private int mPageTextColor = Color.WHITE; + private int mPageTextSize = 56; + private Typeface mPageTextTypeface = Typeface.DEFAULT; + private int mErrorTextColor = Color.RED; + private int mErrorTextSize = 24; + private String mDefaultErrorString = "Error"; + private String mEmptyString = "Empty"; + + public Builder(@NonNull Context context, @NonNull Adapter adapter) { + mContext = context; + mAdapter = adapter; + } + + public Builder setListener(Listener listener) { + mListener = listener; + return this; + } + + public Builder setLayoutMode(@LayoutMode int layoutMode) { + mLayoutMode = layoutMode; + return this; + } + + public Builder setScaleMode(@ScaleMode int scaleMode) { + mScaleMode = scaleMode; + return this; + } + + public Builder setStartPosition(@StartPosition int startPosition) { + mStartPosition = startPosition; + return this; + } + + public Builder setStartPage(int startPage) { + mStartPage = startPage; + return this; + } + + public Builder setBackgroundColor(int backgroundColor) { + mBackgroundColor = backgroundColor; + return this; + } + + public Builder setEdgeColor(int edgeColor) { + mEdgeColor = edgeColor; + return this; + } + + public Builder setPagerInterval(int pagerInterval) { + mPagerInterval = pagerInterval; + return this; + } + + public Builder setScrollInterval(int scrollInterval) { + mScrollInterval = scrollInterval; + return this; + } + + public Builder setPageMinHeight(int pageMinHeight) { + mPageMinHeight = pageMinHeight; + return this; + } + + public Builder setPageInfoInterval(int pageInfoInterval) { + mPageInfoInterval = pageInfoInterval; + return this; + } + + public Builder setProgressColor(int progressColor) { + mProgressColor = progressColor; + return this; + } + + public Builder setProgressSize(int progressSize) { + mProgressSize = progressSize; + return this; + } + + public Builder setPageTextColor(int pageTextColor) { + mPageTextColor = pageTextColor; + return this; + } + + public Builder setPageTextSize(int pageTextSize) { + mPageTextSize = pageTextSize; + return this; + } + + public Builder setPageTextTypeface(Typeface pageTextTypeface) { + mPageTextTypeface = pageTextTypeface; + return this; + } + + public Builder setErrorTextColor(int errorTextColor) { + mErrorTextColor = errorTextColor; + return this; + } + + public Builder setErrorTextSize(int errorTextSize) { + mErrorTextSize = errorTextSize; + return this; + } + + public Builder setDefaultErrorString(String defaultErrorString) { + mDefaultErrorString = defaultErrorString; + return this; + } + + public Builder setEmptyString(String emptyString) { + mEmptyString = emptyString; + return this; + } + + public GalleryView build() { + return new GalleryView(this); + } + } + public static abstract class Adapter { protected GalleryView mGalleryView; @@ -1217,22 +1222,4 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste view.layout(viewLeft, viewTop, viewLeft + viewWidth, viewTop + viewHeight); } } - - public interface Listener { - - @RenderThread - void onUpdateCurrentIndex(int index); - - @RenderThread - void onTapSliderArea(); - - @RenderThread - void onTapMenuArea(); - - @RenderThread - void onTapErrorText(int index); - - @RenderThread - void onLongPressPage(int index); - } } diff --git a/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java b/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java index 50d73b65d..d212488f9 100644 --- a/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java +++ b/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java @@ -29,34 +29,10 @@ import androidx.core.view.GestureDetectorCompat; class GestureRecognizer { @SuppressWarnings("unused") private static final String TAG = "GestureRecognizer"; - - public interface Listener { - boolean onSingleTapUp(float x, float y); - boolean onSingleTapConfirmed(float x, float y); - boolean onDoubleTap(float x, float y); - boolean onDoubleTapConfirmed(float x, float y); - void onLongPress(float x, float y); - boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y); - - /** - * @param velocityX Finger from top to bottom is positive - * @param velocityY Finger from left to right is positive - */ - boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); - boolean onScaleBegin(float focusX, float focusY); - boolean onScale(float focusX, float focusY, float scale); - void onScaleEnd(); - void onDown(float x, float y); - void onUp(); - void onPointerDown(float x, float y); - void onPointerUp(); - } - private final GestureDetectorCompat mGestureDetector; private final ScaleGestureDetector mScaleDetector; private final DownUpDetector mDownUpDetector; private final Listener mListener; - public GestureRecognizer(Context context, Listener listener) { mListener = listener; MyGestureListener gestureListener = new MyGestureListener(); @@ -86,8 +62,42 @@ class GestureRecognizer { cancelEvent.recycle(); } + public interface Listener { + boolean onSingleTapUp(float x, float y); + + boolean onSingleTapConfirmed(float x, float y); + + boolean onDoubleTap(float x, float y); + + boolean onDoubleTapConfirmed(float x, float y); + + void onLongPress(float x, float y); + + boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y); + + /** + * @param velocityX Finger from top to bottom is positive + * @param velocityY Finger from left to right is positive + */ + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); + + boolean onScaleBegin(float focusX, float focusY); + + boolean onScale(float focusX, float focusY, float scale); + + void onScaleEnd(); + + void onDown(float x, float y); + + void onUp(); + + void onPointerDown(float x, float y); + + void onPointerUp(); + } + private class MyGestureListener - extends GestureDetector.SimpleOnGestureListener { + extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { return mListener.onSingleTapUp(e.getX(), e.getY()); @@ -126,7 +136,7 @@ class GestureRecognizer { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { + float velocityY) { return mListener.onFling(e1, e2, velocityX, velocityY); } } diff --git a/app/src/main/java/com/hippo/glgallery/ImageView.java b/app/src/main/java/com/hippo/glgallery/ImageView.java index cd0669ad7..91a1863cc 100644 --- a/app/src/main/java/com/hippo/glgallery/ImageView.java +++ b/app/src/main/java/com/hippo/glgallery/ImageView.java @@ -18,6 +18,7 @@ package com.hippo.glgallery; import android.graphics.Rect; import android.graphics.RectF; + import com.hippo.glview.anim.AlphaAnimation; import com.hippo.glview.glrenderer.GLCanvas; import com.hippo.glview.glrenderer.Texture; @@ -25,48 +26,40 @@ import com.hippo.glview.image.ImageTexture; import com.hippo.glview.view.GLView; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.MathUtils; + import java.util.Arrays; class ImageView extends GLView implements ImageTexture.Callback { - // TODO adjust scale max and min according to image size and screen size - private static final float SCALE_MIN = 1 / 10.0f; - private static final float SCALE_MAX = 10.0f; - public static final int SCALE_ORIGIN = 0; public static final int SCALE_FIT_WIDTH = 1; public static final int SCALE_FIT_HEIGHT = 2; public static final int SCALE_FIT = 3; public static final int SCALE_FIXED = 4; - public static final int START_POSITION_TOP_LEFT = 0; public static final int START_POSITION_TOP_RIGHT = 1; public static final int START_POSITION_BOTTOM_LEFT = 2; public static final int START_POSITION_BOTTOM_RIGHT = 3; public static final int START_POSITION_CENTER = 4; - + // TODO adjust scale max and min according to image size and screen size + private static final float SCALE_MIN = 1 / 10.0f; + private static final float SCALE_MAX = 10.0f; private static final long ALPHA_ANIMATION_DURING = 300L; - - private ImageTexture mImageTexture; - private int mTextureWidth; - private int mTextureHeight; - private final RectF mDst = new RectF(); private final RectF mSrcActual = new RectF(); private final RectF mDstActual = new RectF(); private final Rect mValidRect = new Rect(); - + private final AlphaAnimation mAlphaAnimation; + private ImageTexture mImageTexture; + private int mTextureWidth; + private int mTextureHeight; private int mScaleMode = SCALE_FIT; private int mStartPosition = START_POSITION_TOP_RIGHT; private float mScaleValue = 1.0f; - private float mScale = 1.0f; - private boolean mScaleOffsetDirty = true; private boolean mPositionInRootDirty = true; - private final AlphaAnimation mAlphaAnimation; - public ImageView() { mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f); mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING); @@ -186,6 +179,10 @@ class ImageView extends GLView implements ImageTexture.Callback { Arrays.sort(scaleDefault); } + public ImageTexture getImageTexture() { + return mImageTexture; + } + public void setImageTexture(ImageTexture imageTexture) { // Remove callback if (mImageTexture != null) { @@ -229,10 +226,6 @@ class ImageView extends GLView implements ImageTexture.Callback { } } - public ImageTexture getImageTexture() { - return mImageTexture; - } - public boolean isLoaded() { return mImageTexture != null; } diff --git a/app/src/main/java/com/hippo/glgallery/PagerLayoutManager.java b/app/src/main/java/com/hippo/glgallery/PagerLayoutManager.java index b7905613c..0945e01b9 100644 --- a/app/src/main/java/com/hippo/glgallery/PagerLayoutManager.java +++ b/app/src/main/java/com/hippo/glgallery/PagerLayoutManager.java @@ -33,20 +33,15 @@ import com.hippo.glview.widget.GLTextureView; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.MathUtils; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; class PagerLayoutManager extends GalleryView.LayoutManager { - private static final String TAG = PagerLayoutManager.class.getSimpleName(); - - @IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT}) - @Retention(RetentionPolicy.SOURCE) - private @interface Mode {} - public static final int MODE_LEFT_TO_RIGHT = 0; public static final int MODE_RIGHT_TO_LEFT = 1; - + private static final String TAG = PagerLayoutManager.class.getSimpleName(); private static final Interpolator SMOOTH_SCROLLER_INTERPOLATOR = new Interpolator() { @Override public float getInterpolation(float t) { @@ -54,45 +49,36 @@ class PagerLayoutManager extends GalleryView.LayoutManager { return t * t * t * t * t + 1.0f; } }; - + private final int[] mScrollRemain = new int[2]; + private final float[] mScaleDefault = new float[4]; + private final SmoothScroller mSmoothScroller; + private final PageFling mPageFling; + private final SmoothScaler mSmoothScaler; + private final OverScroller mOverScroller; + private final Rect mTempRect = new Rect(); private GalleryView.Adapter mAdapter; - private GLProgressView mProgress; private String mErrorStr; private GLTextureView mErrorView; private GalleryPageView mPrevious; private GalleryPageView mCurrent; private GalleryPageView mNext; - @Mode private int mMode = MODE_RIGHT_TO_LEFT; private int mScaleMode; private int mStartPosition; private float mScaleValue; - private int mOffset; private int mDeltaX; private int mDeltaY; private boolean mCanScrollBetweenPages = false; private boolean mStopAnimationFinger; - private int mInterval; - - private final int[] mScrollRemain = new int[2]; - private final float[] mScaleDefault = new float[4]; - - private final SmoothScroller mSmoothScroller; - private final PageFling mPageFling; - private final SmoothScaler mSmoothScaler; - private final OverScroller mOverScroller; - // Current index private int mIndex; - private final Rect mTempRect = new Rect(); - public PagerLayoutManager(Context context, @NonNull GalleryView galleryView, - int scaleMode, int startPoint, float scaleValue, int interval) { + int scaleMode, int startPoint, float scaleValue, int interval) { super(galleryView); mScaleMode = scaleMode; @@ -292,7 +278,7 @@ class PagerLayoutManager extends GalleryView.LayoutManager { } private void layoutPage(GalleryPageView page, int widthSpec, int heightSpec, - int left, int top, int right, int bottom) { + int left, int top, int right, int bottom) { Rect rect = mTempRect; page.getValidRect(rect); boolean oldValid = !rect.isEmpty(); @@ -465,7 +451,7 @@ class PagerLayoutManager extends GalleryView.LayoutManager { image.getScaleDefault(scales); float scale = image.getScale(); float endScale = scales[0]; - for (float value: scales) { + for (float value : scales) { if (scale < value - 0.01f) { endScale = value; break; @@ -476,7 +462,8 @@ class PagerLayoutManager extends GalleryView.LayoutManager { } @Override - public void onLongPress(float x, float y) {} + public void onLongPress(float x, float y) { + } private void pagePrevious() { if (mIndex <= 0) { @@ -574,7 +561,7 @@ class PagerLayoutManager extends GalleryView.LayoutManager { if (rightPage == null) { limit = 0; } else { - limit = - width - mInterval; + limit = -width - mInterval; } if (dx < mOffset - limit) { @@ -877,6 +864,11 @@ class PagerLayoutManager extends GalleryView.LayoutManager { return currentIndex; } + @IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT}) + @Retention(RetentionPolicy.SOURCE) + private @interface Mode { + } + private class SmoothScroller extends Animation { private int mDx; @@ -920,20 +912,20 @@ class PagerLayoutManager extends GalleryView.LayoutManager { private class PageFling extends Fling { + private final int[] mTemp = new int[2]; private int mVelocityX; private int mVelocityY; private int mDx; private int mDy; private int mLastX; private int mLastY; - private final int[] mTemp = new int[2]; public PageFling(Context context) { super(context); } public void startFling(int velocityX, int minX, int maxX, - int velocityY, int minY, int maxY) { + int velocityY, int minY, int maxY) { mVelocityX = velocityX; mVelocityY = velocityY; mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX)); @@ -1024,7 +1016,7 @@ class PagerLayoutManager extends GalleryView.LayoutManager { } public void startSmoothScaler(float focusX, float focusY, - float startScale, float endScale, int duration) { + float startScale, float endScale, int duration) { mFocusX = focusX; mFocusY = focusY; mStartScale = startScale; diff --git a/app/src/main/java/com/hippo/glgallery/ScrollLayoutManager.java b/app/src/main/java/com/hippo/glgallery/ScrollLayoutManager.java index 0e9eac717..1d0cdceed 100644 --- a/app/src/main/java/com/hippo/glgallery/ScrollLayoutManager.java +++ b/app/src/main/java/com/hippo/glgallery/ScrollLayoutManager.java @@ -31,6 +31,7 @@ import com.hippo.glview.widget.GLTextureView; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.MathUtils; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -46,15 +47,15 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { private static final float SCALE_ERROR = 0.01f; private static final int INVALID_TOP = Integer.MAX_VALUE; - + private final LinkedList mPages = new LinkedList<>(); + private final LinkedList mTempPages = new LinkedList<>(); + private final PageFling mPageFling; + private final SmoothScaler mSmoothScaler; + private final OverScroller mOverScroller; private GalleryView.Adapter mAdapter; - private GLProgressView mProgress; private String mErrorStr; private GLTextureView mErrorView; - private final LinkedList mPages = new LinkedList<>(); - private final LinkedList mTempPages = new LinkedList<>(); - private float mScale = 0.5f; private int mOffsetX; private int mOffsetY; @@ -66,13 +67,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { private boolean mScrollUp; private boolean mFlingUp; private boolean mStopAnimationFinger; - private int mInterval; - - private final PageFling mPageFling; - private final SmoothScaler mSmoothScaler; - private final OverScroller mOverScroller; - // Current index private int mIndex; @@ -195,7 +190,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { } private GalleryPageView getPageForIndex(List pages, int index, boolean remove) { - for (Iterator iterator = pages.iterator(); iterator.hasNext();) { + for (Iterator iterator = pages.iterator(); iterator.hasNext(); ) { GalleryPageView page = iterator.next(); if (index == page.getIndex()) { if (remove) { @@ -341,7 +336,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { page = pages.getFirst(); if (0 == page.getIndex() && page.bounds().top > 0) { int offset = -page.bounds().top; - for (GalleryPageView p: pages) { + for (GalleryPageView p : pages) { p.offsetTopAndBottom(offset); } topOffset += offset; @@ -390,7 +385,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { page.measure(widthSpec, heightSpec); int offset = Math.min(height - pagesBottom, page.getMeasuredHeight()); - for (GalleryPageView p: pages) { + for (GalleryPageView p : pages) { p.offsetTopAndBottom(offset); } int bottom = pagesTop - interval + offset; @@ -885,7 +880,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { // Get first shown page GalleryPageView previousPage = null; GalleryPageView firstShownPage = null; - for (GalleryPageView p: mPages) { + for (GalleryPageView p : mPages) { if (isInScreen(p)) { firstShownPage = p; break; @@ -940,7 +935,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { // Get first shown page GalleryPageView lastShownPage = null; GalleryPageView nextPage = null; - for (GalleryPageView p: mPages) { + for (GalleryPageView p : mPages) { if (isInScreen(p)) { lastShownPage = p; } else if (null != lastShownPage) { @@ -1019,7 +1014,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { GalleryPageView targetPage = null; for (GalleryPageView page : mPages) { if (page.getIndex() == index) { - targetPage =page; + targetPage = page; break; } } @@ -1084,7 +1079,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { } public void startFling(int velocityX, int minX, int maxX, - int velocityY, int minY, int maxY) { + int velocityY, int minY, int maxY) { mVelocityX = velocityX; mVelocityY = velocityY; mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX)); @@ -1173,7 +1168,7 @@ class ScrollLayoutManager extends GalleryView.LayoutManager { } public void startSmoothScaler(float focusX, float focusY, - float startScale, float endScale, int duration) { + float startScale, float endScale, int duration) { mFocusX = focusX; mFocusY = focusY; mStartScale = startScale; diff --git a/app/src/main/java/com/hippo/glview/anim/Animation.java b/app/src/main/java/com/hippo/glview/anim/Animation.java index 913b962fe..5af48b333 100644 --- a/app/src/main/java/com/hippo/glview/anim/Animation.java +++ b/app/src/main/java/com/hippo/glview/anim/Animation.java @@ -47,9 +47,6 @@ import com.hippo.yorozuya.MathUtils; // The start() method can be called again to restart the Animation. // abstract public class Animation { - private static final long ANIMATION_START = -1; - private static final long NO_ANIMATION = -2; - /** * When the animation reaches the end and repeatCount is INFINITE * or a positive value, the animation restarts from the beginning. @@ -65,7 +62,8 @@ abstract public class Animation { * the animation indefinitely. */ public static final int INFINITE = -1; - + private static final long ANIMATION_START = -1; + private static final long NO_ANIMATION = -2; private long mStartTime = NO_ANIMATION; private long mDuration; private Interpolator mInterpolator; diff --git a/app/src/main/java/com/hippo/glview/anim/CanvasAnimation.java b/app/src/main/java/com/hippo/glview/anim/CanvasAnimation.java index 9ccfeea14..f6ec87d1b 100644 --- a/app/src/main/java/com/hippo/glview/anim/CanvasAnimation.java +++ b/app/src/main/java/com/hippo/glview/anim/CanvasAnimation.java @@ -21,5 +21,6 @@ import com.hippo.glview.glrenderer.GLCanvas; public abstract class CanvasAnimation extends Animation { public abstract int getCanvasSaveFlags(); + public abstract void apply(GLCanvas canvas); } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/BasicTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/BasicTexture.java index 5dcd5975b..d6c3a9eba 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/BasicTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/BasicTexture.java @@ -28,31 +28,24 @@ import java.util.WeakHashMap; // If a BasicTexture is loaded into GL memory, it has a GL texture id. public abstract class BasicTexture implements Texture { - private static final String TAG = "BasicTexture"; protected static final int UNSPECIFIED = -1; - protected static final int STATE_UNLOADED = 0; protected static final int STATE_LOADED = 1; protected static final int STATE_ERROR = -1; - + private static final String TAG = "BasicTexture"; // Log a warning if a texture is larger along a dimension private static final int MAX_TEXTURE_SIZE = 4096; - - protected int mId = -1; - protected int mState; - - protected int mWidth = UNSPECIFIED; - protected int mHeight = UNSPECIFIED; - - protected int mTextureWidth; - protected int mTextureHeight; - - private boolean mHasBorder; - - protected GLCanvas mCanvasRef = null; private final static WeakHashMap sAllTextures = new WeakHashMap<>(); private static final ThreadLocal sInFinalizer = new ThreadLocal<>(); + protected int mId = -1; + protected int mState; + protected int mWidth = UNSPECIFIED; + protected int mHeight = UNSPECIFIED; + protected int mTextureWidth; + protected int mTextureHeight; + protected GLCanvas mCanvasRef = null; + private boolean mHasBorder; protected BasicTexture(GLCanvas canvas, int id, int state) { setAssociatedCanvas(canvas); @@ -67,6 +60,30 @@ public abstract class BasicTexture implements Texture { this(null, 0, STATE_UNLOADED); } + // This is for deciding if we can call Bitmap's recycle(). + // We cannot call Bitmap's recycle() in finalizer because at that point + // the finalizer of Bitmap may already be called so recycle() will crash. + public static boolean inFinalizer() { + return sInFinalizer.get() != null; + } + + public static void yieldAllTextures() { + synchronized (sAllTextures) { + for (BasicTexture t : sAllTextures.keySet()) { + t.yield(); + } + } + } + + public static void invalidateAllTextures() { + synchronized (sAllTextures) { + for (BasicTexture t : sAllTextures.keySet()) { + t.mState = STATE_UNLOADED; + t.setAssociatedCanvas(null); + } + } + } + protected void setAssociatedCanvas(GLCanvas canvas) { mCanvasRef = canvas; } @@ -87,7 +104,7 @@ public abstract class BasicTexture implements Texture { } public boolean isFlippedVertically() { - return false; + return false; } public int getId() { @@ -194,28 +211,4 @@ public abstract class BasicTexture implements Texture { super.finalize(); } } - - // This is for deciding if we can call Bitmap's recycle(). - // We cannot call Bitmap's recycle() in finalizer because at that point - // the finalizer of Bitmap may already be called so recycle() will crash. - public static boolean inFinalizer() { - return sInFinalizer.get() != null; - } - - public static void yieldAllTextures() { - synchronized (sAllTextures) { - for (BasicTexture t : sAllTextures.keySet()) { - t.yield(); - } - } - } - - public static void invalidateAllTextures() { - synchronized (sAllTextures) { - for (BasicTexture t : sAllTextures.keySet()) { - t.mState = STATE_UNLOADED; - t.setAssociatedCanvas(null); - } - } - } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/FadeTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/FadeTexture.java index 7cda34c06..c8783343f 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/FadeTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/FadeTexture.java @@ -69,7 +69,7 @@ public abstract class FadeTexture implements Texture { } protected float getRatio() { - float r = (float)(now() - mStartTime) / DURATION; + float r = (float) (now() - mStartTime) / DURATION; return MathUtils.clamp(1.0f - r, 0.0f, 1.0f); } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLCanvas.java b/app/src/main/java/com/hippo/glview/glrenderer/GLCanvas.java index 3d5d36f4e..56fd025bc 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLCanvas.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLCanvas.java @@ -28,6 +28,10 @@ import android.graphics.RectF; // public interface GLCanvas { + int SAVE_FLAG_ALL = 0xFFFFFFFF; + int SAVE_FLAG_ALPHA = 0x01; + int SAVE_FLAG_MATRIX = 0x02; + GLId getGLId(); // Tells GLCanvas the size of the underlying GL surface. This should be @@ -42,11 +46,11 @@ public interface GLCanvas { void clearBuffer(float[] argb); + float getAlpha(); + // Sets and gets the current alpha, alpha must be in [0, 1]. void setAlpha(float alpha); - float getAlpha(); - // (current alpha) = (current alpha) * alpha void multiplyAlpha(float alpha); @@ -68,10 +72,6 @@ public interface GLCanvas { // Same as save(), but only save those specified in saveFlags. void save(int saveFlags); - int SAVE_FLAG_ALL = 0xFFFFFFFF; - int SAVE_FLAG_ALPHA = 0x01; - int SAVE_FLAG_MATRIX = 0x02; - // Pops from the top of the stack as current configuration state (matrix, // alpha, and clip). This call balances a previous call to save(), and is // used to remove all modifications to the configuration state since the @@ -89,11 +89,11 @@ public interface GLCanvas { // Draws a oval using the specified paint for (cx, cy, radiusX, radiusY) // (Both end points are included). void drawOval(float cx, float cy, float radiusX, float radiusY, - GLPaint paint); + GLPaint paint); // Draw a arc inside a rect void drawArc(float cx, float cy, float radiusX, float radiusY, - float sweepAngle, GLPaint paint); + float sweepAngle, GLPaint paint); // Fills the specified rectangle with the specified color. void fillRect(float x, float y, float width, float height, int color); @@ -160,8 +160,8 @@ public interface GLCanvas { * Initializes the texture to a size by calling texImage2D on it. * * @param texture The texture to initialize the size. - * @param format The texture format (e.g. GL_RGBA) - * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) */ void initializeTextureSize(BasicTexture texture, int format, int type); @@ -169,7 +169,7 @@ public interface GLCanvas { * Initializes the texture to a size by calling texImage2D on it. * * @param texture The texture to initialize the size. - * @param bitmap The bitmap to initialize the bitmap with. + * @param bitmap The bitmap to initialize the bitmap with. */ void initializeTexture(BasicTexture texture, Bitmap bitmap); @@ -178,11 +178,11 @@ public interface GLCanvas { * * @param texture The target texture to write to. * @param xOffset Specifies a texel offset in the x direction within the - * texture array. + * texture array. * @param yOffset Specifies a texel offset in the y direction within the - * texture array. - * @param format The texture format (e.g. GL_RGBA) - * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + * texture array. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) */ void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type); @@ -214,9 +214,9 @@ public interface GLCanvas { * It only considers the lower-left and upper-right corners as the bounds. * * @param bounds The output bounds to write to. - * @param x The left side of the input rectangle. - * @param y The bottom of the input rectangle. - * @param width The width of the input rectangle. + * @param x The left side of the input rectangle. + * @param y The bottom of the input rectangle. + * @param width The width of the input rectangle. * @param height The height of the input rectangle. */ void getBounds(Rect bounds, int x, int y, int width, int height); diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLES11Canvas.java b/app/src/main/java/com/hippo/glview/glrenderer/GLES11Canvas.java index 567f94747..eee4d4b8e 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLES11Canvas.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLES11Canvas.java @@ -24,13 +24,16 @@ import android.opengl.GLU; import android.opengl.GLUtils; import android.opengl.Matrix; import android.util.Log; + import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.collect.IntList; + import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; + import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; @@ -54,20 +57,27 @@ public class GLES11Canvas implements GLCanvas { private static final int OFFSET_LAST = OFFSET_DRAW_CIRCLE + COUNT_CIRCLE_VERTEX + 1; private static final float[] BOX_COORDINATES = new float[OFFSET_LAST * 2]; + // TODO: the code only work for 2D should get fixed for 3D or removed + private static final int MSKEW_X = 4; + private static final int MSKEW_Y = 1; + private static final int MSCALE_X = 0; + private static final int MSCALE_Y = 5; + private static float[] sCropRect = new float[4]; + private static GLId mGLId = new GLES11IdImpl(); static { float[] temp = { - 0, 0, // Fill rectangle - 1, 0, - 0, 1, - 1, 1, - 0, 0, // Draw line - 1, 1, - 0, 0, // Draw rectangle outline - 0, 1, - 1, 1, - 1, 0, - 0.5f, 0.5f // Fill circle + 0, 0, // Fill rectangle + 1, 0, + 0, 1, + 1, 1, + 0, 0, // Draw line + 1, 1, + 0, 0, // Draw rectangle outline + 0, 1, + 1, 1, + 1, 0, + 0.5f, 0.5f // Fill circle }; System.arraycopy(temp, 0, BOX_COORDINATES, 0, temp.length); @@ -88,47 +98,35 @@ public class GLES11Canvas implements GLCanvas { } } - private GL11 mGL; - private final float mMatrixValues[] = new float[16]; private final float mTextureMatrixValues[] = new float[16]; - // The results of mapPoints are stored in this buffer, and the order is // x1, y1, x2, y2. private final float mMapPointsBuffer[] = new float[4]; - private final float mTextureColor[] = new float[4]; - - private int mBoxCoords; - - private GLState mGLState; private final ArrayList mTargetStack = new ArrayList(); - - private float mAlpha; private final ArrayList mRestoreStack = new ArrayList(); - private ConfigState mRecycledRestoreAction; - private final RectF mDrawTextureSourceRect = new RectF(); private final RectF mDrawTextureTargetRect = new RectF(); private final float[] mTempMatrix = new float[32]; private final IntList mUnboundTextures = new IntList(); private final IntList mDeleteBuffers = new IntList(); - private int mScreenWidth; - private int mScreenHeight; - private boolean mBlendEnabled = true; - private int mFrameBuffer[] = new int[1]; - private static float[] sCropRect = new float[4]; - - private RawTexture mTargetTexture; - // Drawing statistics int mCountDrawLine; int mCountFillRect; int mCountDrawMesh; int mCountTextureRect; int mCountTextureOES; - - private static GLId mGLId = new GLES11IdImpl(); + private GL11 mGL; + private int mBoxCoords; + private GLState mGLState; + private float mAlpha; + private ConfigState mRecycledRestoreAction; + private int mScreenWidth; + private int mScreenHeight; + private boolean mBlendEnabled = true; + private int mFrameBuffer[] = new int[1]; + private RawTexture mTargetTexture; public GLES11Canvas(GL11 gl) { mGL = gl; @@ -158,6 +156,80 @@ public class GLES11Canvas implements GLCanvas { // mMatrixValues and mAlpha will be initialized in setSize() } + private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, + BasicTexture texture) { + + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * + (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * + (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + private static boolean isMatrixRotatedOrFlipped(float matrix[]) { + final float eps = 1e-5f; + return Math.abs(matrix[MSKEW_X]) > eps + || Math.abs(matrix[MSKEW_Y]) > eps + || matrix[MSCALE_X] < -eps + || matrix[MSCALE_Y] > eps; + } + + private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) { + int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES); + if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) { + String msg = ""; + switch (status) { + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES: + msg = "FRAMEBUFFER_FORMATS"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES: + msg = "FRAMEBUFFER_ATTACHMENT"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES: + msg = "FRAMEBUFFER_MISSING_ATTACHMENT"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES: + msg = "FRAMEBUFFER_DRAW_BUFFER"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES: + msg = "FRAMEBUFFER_READ_BUFFER"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES: + msg = "FRAMEBUFFER_UNSUPPORTED"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES: + msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + @Override public void setSize(int width, int height) { if (mTargetTexture == null) { @@ -184,23 +256,19 @@ public class GLES11Canvas implements GLCanvas { } } - @Override - public void setAlpha(float alpha) { - mAlpha = alpha; - } - @Override public float getAlpha() { return mAlpha; } @Override - public void multiplyAlpha(float alpha) { - mAlpha *= alpha; + public void setAlpha(float alpha) { + mAlpha = alpha; } - private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { - return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + @Override + public void multiplyAlpha(float alpha) { + mAlpha *= alpha; } @Override @@ -362,7 +430,7 @@ public class GLES11Canvas implements GLCanvas { @Override public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, - int uvBuffer, int indexBuffer, int indexCount) { + int uvBuffer, int indexBuffer, int indexCount) { float alpha = mAlpha; if (!bindTexture(tex)) return; @@ -458,7 +526,7 @@ public class GLES11Canvas implements GLCanvas { } private void drawTexture(BasicTexture texture, - int x, int y, int width, int height, float alpha) { + int x, int y, int width, int height, float alpha) { if (width <= 0 || height <= 0) return; mGLState.setBlendEnabled(mBlendEnabled @@ -489,7 +557,7 @@ public class GLES11Canvas implements GLCanvas { @Override public void drawTexture(BasicTexture texture, float[] mTextureTransform, - int x, int y, int w, int h) { + int x, int y, int w, int h) { mGLState.setBlendEnabled(mBlendEnabled && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA)); if (!bindTexture(texture)) return; @@ -498,40 +566,9 @@ public class GLES11Canvas implements GLCanvas { textureRect(x, y, w, h); } - // This function changes the source coordinate to the texture coordinates. - // It also clips the source and target coordinates if it is beyond the - // bound of the texture. - private static void convertCoordinate(RectF source, RectF target, - BasicTexture texture) { - - int width = texture.getWidth(); - int height = texture.getHeight(); - int texWidth = texture.getTextureWidth(); - int texHeight = texture.getTextureHeight(); - // Convert to texture coordinates - source.left /= texWidth; - source.right /= texWidth; - source.top /= texHeight; - source.bottom /= texHeight; - - // Clip if the rendering range is beyond the bound of the texture. - float xBound = (float) width / texWidth; - if (source.right > xBound) { - target.right = target.left + target.width() * - (xBound - source.left) / source.width(); - source.right = xBound; - } - float yBound = (float) height / texHeight; - if (source.bottom > yBound) { - target.bottom = target.top + target.height() * - (yBound - source.top) / source.height(); - source.bottom = yBound; - } - } - @Override public void drawMixed(BasicTexture from, - int toColor, float ratio, int x, int y, int w, int h) { + int toColor, float ratio, int x, int y, int w, int h) { drawMixed(from, toColor, ratio, x, y, w, h, mAlpha); } @@ -594,7 +631,7 @@ public class GLES11Canvas implements GLCanvas { @Override public void drawMixed(BasicTexture from, int toColor, float ratio, - RectF source, RectF target) { + RectF source, RectF target) { if (target.width() <= 0 || target.height() <= 0) return; if (ratio <= 0.01f) { @@ -628,7 +665,7 @@ public class GLES11Canvas implements GLCanvas { } private void drawMixed(BasicTexture from, int toColor, - float ratio, int x, int y, int width, int height, float alpha) { + float ratio, int x, int y, int width, int height, float alpha) { // change from 0 to 0.01f to prevent getting divided by zero below if (ratio <= 0.01f) { drawTexture(from, x, y, width, height, alpha); @@ -652,18 +689,270 @@ public class GLES11Canvas implements GLCanvas { mGLState.setTexEnvMode(GL11.GL_REPLACE); } - // TODO: the code only work for 2D should get fixed for 3D or removed - private static final int MSKEW_X = 4; - private static final int MSKEW_Y = 1; - private static final int MSCALE_X = 0; - private static final int MSCALE_Y = 5; + @Override + public void clearBuffer(float[] argb) { + if (argb != null && argb.length == 4) { + mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); + } else { + mGL.glClearColor(0, 0, 0, 1); + } + mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); + } - private static boolean isMatrixRotatedOrFlipped(float matrix[]) { - final float eps = 1e-5f; - return Math.abs(matrix[MSKEW_X]) > eps - || Math.abs(matrix[MSKEW_Y]) > eps - || matrix[MSCALE_X] < -eps - || matrix[MSCALE_Y] > eps; + @Override + public void clearBuffer() { + clearBuffer(null); + } + + private void setTextureCoords(RectF source) { + setTextureCoords(source.left, source.top, source.right, source.bottom); + } + + private void setTextureCoords(float left, float top, + float right, float bottom) { + mGL.glMatrixMode(GL11.GL_TEXTURE); + mTextureMatrixValues[0] = right - left; + mTextureMatrixValues[5] = bottom - top; + mTextureMatrixValues[10] = 1; + mTextureMatrixValues[12] = left; + mTextureMatrixValues[13] = top; + mTextureMatrixValues[15] = 1; + mGL.glLoadMatrixf(mTextureMatrixValues, 0); + mGL.glMatrixMode(GL11.GL_MODELVIEW); + } + + private void setTextureCoords(float[] mTextureTransform) { + mGL.glMatrixMode(GL11.GL_TEXTURE); + mGL.glLoadMatrixf(mTextureTransform, 0); + mGL.glMatrixMode(GL11.GL_MODELVIEW); + } + + // unloadTexture and deleteBuffer can be called from the finalizer thread, + // so we synchronized on the mUnboundTextures object. + @Override + public boolean unloadTexture(BasicTexture t) { + synchronized (mUnboundTextures) { + if (!t.isLoaded()) return false; + mUnboundTextures.add(t.mId); + return true; + } + } + + @Override + public void deleteBuffer(int bufferId) { + synchronized (mUnboundTextures) { + mDeleteBuffers.add(bufferId); + } + } + + @Override + public void deleteRecycledResources() { + synchronized (mUnboundTextures) { + IntList ids = mUnboundTextures; + if (ids.size() > 0) { + mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + + ids = mDeleteBuffers; + if (ids.size() > 0) { + mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + } + } + + @Override + public void save() { + save(SAVE_FLAG_ALL); + } + + @Override + public void save(int saveFlags) { + ConfigState config = obtainRestoreConfig(); + + if ((saveFlags & SAVE_FLAG_ALPHA) != 0) { + config.mAlpha = mAlpha; + } else { + config.mAlpha = -1; + } + + if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { + System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); + } else { + config.mMatrix[0] = Float.NEGATIVE_INFINITY; + } + + mRestoreStack.add(config); + } + + @Override + public void restore() { + if (mRestoreStack.isEmpty()) throw new IllegalStateException(); + ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1); + config.restore(this); + freeRestoreConfig(config); + } + + private void freeRestoreConfig(ConfigState action) { + action.mNextFree = mRecycledRestoreAction; + mRecycledRestoreAction = action; + } + + private ConfigState obtainRestoreConfig() { + if (mRecycledRestoreAction != null) { + ConfigState result = mRecycledRestoreAction; + mRecycledRestoreAction = result.mNextFree; + return result; + } + return new ConfigState(); + } + + @Override + public void dumpStatisticsAndClear() { + String line = String.format( + "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", + mCountDrawMesh, mCountTextureRect, mCountTextureOES, + mCountFillRect, mCountDrawLine); + mCountDrawMesh = 0; + mCountTextureRect = 0; + mCountTextureOES = 0; + mCountFillRect = 0; + mCountDrawLine = 0; + Log.d(TAG, line); + } + + private void saveTransform() { + System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16); + } + + private void restoreTransform() { + System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16); + } + + private void setRenderTarget(RawTexture texture) { + GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL; + + if (mTargetTexture == null && texture != null) { + mGLId.glGenBuffers(1, mFrameBuffer, 0); + gl11ep.glBindFramebufferOES( + GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]); + } + if (mTargetTexture != null && texture == null) { + gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0); + gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0); + } + + mTargetTexture = texture; + if (texture == null) { + setSize(mScreenWidth, mScreenHeight); + } else { + setSize(texture.getWidth(), texture.getHeight()); + + if (!texture.isLoaded()) texture.prepare(this); + + gl11ep.glFramebufferTexture2DOES( + GL11ExtensionPack.GL_FRAMEBUFFER_OES, + GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES, + GL11.GL_TEXTURE_2D, texture.getId(), 0); + + checkFramebufferStatus(gl11ep); + } + } + + @Override + public void endRenderTarget() { + RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1); + setRenderTarget(texture); + restore(); // restore matrix and alpha + } + + @Override + public void beginRenderTarget(RawTexture texture) { + save(); // save matrix and alpha + mTargetStack.add(mTargetTexture); + setRenderTarget(texture); + } + + @Override + public void setTextureParameters(BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + // Define a vertically flipped crop rectangle for OES_draw_texture. + // The four values in sCropRect are: left, bottom, width, and + // height. Negative value of width or height means flip. + sCropRect[0] = 0; + sCropRect[1] = height; + sCropRect[2] = width; + sCropRect[3] = -height; + + // Set texture parameters. + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, Float.SIZE / Byte.SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buf, int elementSize) { + int[] bufferIds = new int[1]; + mGLId.glGenBuffers(bufferIds.length, bufferIds, 0); + int bufferId = bufferIds[0]; + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); + mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf, + GL11.GL_STATIC_DRAW); + return bufferId; + } + + @Override + public void recoverFromLightCycle() { + // This is only required for GLES20 + } + + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + // This is only required for GLES20 + } + + @Override + public GLId getGLId() { + return mGLId; } private static class GLState { @@ -768,125 +1057,6 @@ public class GLES11Canvas implements GLCanvas { } } - @Override - public void clearBuffer(float[] argb) { - if(argb != null && argb.length == 4) { - mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); - } else { - mGL.glClearColor(0, 0, 0, 1); - } - mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); - } - - @Override - public void clearBuffer() { - clearBuffer(null); - } - - private void setTextureCoords(RectF source) { - setTextureCoords(source.left, source.top, source.right, source.bottom); - } - - private void setTextureCoords(float left, float top, - float right, float bottom) { - mGL.glMatrixMode(GL11.GL_TEXTURE); - mTextureMatrixValues[0] = right - left; - mTextureMatrixValues[5] = bottom - top; - mTextureMatrixValues[10] = 1; - mTextureMatrixValues[12] = left; - mTextureMatrixValues[13] = top; - mTextureMatrixValues[15] = 1; - mGL.glLoadMatrixf(mTextureMatrixValues, 0); - mGL.glMatrixMode(GL11.GL_MODELVIEW); - } - - private void setTextureCoords(float[] mTextureTransform) { - mGL.glMatrixMode(GL11.GL_TEXTURE); - mGL.glLoadMatrixf(mTextureTransform, 0); - mGL.glMatrixMode(GL11.GL_MODELVIEW); - } - - // unloadTexture and deleteBuffer can be called from the finalizer thread, - // so we synchronized on the mUnboundTextures object. - @Override - public boolean unloadTexture(BasicTexture t) { - synchronized (mUnboundTextures) { - if (!t.isLoaded()) return false; - mUnboundTextures.add(t.mId); - return true; - } - } - - @Override - public void deleteBuffer(int bufferId) { - synchronized (mUnboundTextures) { - mDeleteBuffers.add(bufferId); - } - } - - @Override - public void deleteRecycledResources() { - synchronized (mUnboundTextures) { - IntList ids = mUnboundTextures; - if (ids.size() > 0) { - mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); - ids.clear(); - } - - ids = mDeleteBuffers; - if (ids.size() > 0) { - mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); - ids.clear(); - } - } - } - - @Override - public void save() { - save(SAVE_FLAG_ALL); - } - - @Override - public void save(int saveFlags) { - ConfigState config = obtainRestoreConfig(); - - if ((saveFlags & SAVE_FLAG_ALPHA) != 0) { - config.mAlpha = mAlpha; - } else { - config.mAlpha = -1; - } - - if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { - System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); - } else { - config.mMatrix[0] = Float.NEGATIVE_INFINITY; - } - - mRestoreStack.add(config); - } - - @Override - public void restore() { - if (mRestoreStack.isEmpty()) throw new IllegalStateException(); - ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1); - config.restore(this); - freeRestoreConfig(config); - } - - private void freeRestoreConfig(ConfigState action) { - action.mNextFree = mRecycledRestoreAction; - mRecycledRestoreAction = action; - } - - private ConfigState obtainRestoreConfig() { - if (mRecycledRestoreAction != null) { - ConfigState result = mRecycledRestoreAction; - mRecycledRestoreAction = result.mNextFree; - return result; - } - return new ConfigState(); - } - private static class ConfigState { float mAlpha; float mMatrix[] = new float[16]; @@ -899,182 +1069,4 @@ public class GLES11Canvas implements GLCanvas { } } } - - @Override - public void dumpStatisticsAndClear() { - String line = String.format( - "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", - mCountDrawMesh, mCountTextureRect, mCountTextureOES, - mCountFillRect, mCountDrawLine); - mCountDrawMesh = 0; - mCountTextureRect = 0; - mCountTextureOES = 0; - mCountFillRect = 0; - mCountDrawLine = 0; - Log.d(TAG, line); - } - - private void saveTransform() { - System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16); - } - - private void restoreTransform() { - System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16); - } - - private void setRenderTarget(RawTexture texture) { - GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL; - - if (mTargetTexture == null && texture != null) { - mGLId.glGenBuffers(1, mFrameBuffer, 0); - gl11ep.glBindFramebufferOES( - GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]); - } - if (mTargetTexture != null && texture == null) { - gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0); - gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0); - } - - mTargetTexture = texture; - if (texture == null) { - setSize(mScreenWidth, mScreenHeight); - } else { - setSize(texture.getWidth(), texture.getHeight()); - - if (!texture.isLoaded()) texture.prepare(this); - - gl11ep.glFramebufferTexture2DOES( - GL11ExtensionPack.GL_FRAMEBUFFER_OES, - GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES, - GL11.GL_TEXTURE_2D, texture.getId(), 0); - - checkFramebufferStatus(gl11ep); - } - } - - @Override - public void endRenderTarget() { - RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1); - setRenderTarget(texture); - restore(); // restore matrix and alpha - } - - @Override - public void beginRenderTarget(RawTexture texture) { - save(); // save matrix and alpha - mTargetStack.add(mTargetTexture); - setRenderTarget(texture); - } - - private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) { - int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES); - if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) { - String msg = ""; - switch (status) { - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES: - msg = "FRAMEBUFFER_FORMATS"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES: - msg = "FRAMEBUFFER_ATTACHMENT"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES: - msg = "FRAMEBUFFER_MISSING_ATTACHMENT"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES: - msg = "FRAMEBUFFER_DRAW_BUFFER"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES: - msg = "FRAMEBUFFER_READ_BUFFER"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES: - msg = "FRAMEBUFFER_UNSUPPORTED"; - break; - case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES: - msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; - break; - } - throw new RuntimeException(msg + ":" + Integer.toHexString(status)); - } - } - - @Override - public void setTextureParameters(BasicTexture texture) { - int width = texture.getWidth(); - int height = texture.getHeight(); - // Define a vertically flipped crop rectangle for OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = 0; - sCropRect[1] = height; - sCropRect[2] = width; - sCropRect[3] = -height; - - // Set texture parameters. - int target = texture.getTarget(); - mGL.glBindTexture(target, texture.getId()); - mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - } - - @Override - public void initializeTextureSize(BasicTexture texture, int format, int type) { - int target = texture.getTarget(); - mGL.glBindTexture(target, texture.getId()); - int width = texture.getTextureWidth(); - int height = texture.getTextureHeight(); - mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null); - } - - @Override - public void initializeTexture(BasicTexture texture, Bitmap bitmap) { - int target = texture.getTarget(); - mGL.glBindTexture(target, texture.getId()); - GLUtils.texImage2D(target, 0, bitmap, 0); - } - - @Override - public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, - int format, int type) { - int target = texture.getTarget(); - mGL.glBindTexture(target, texture.getId()); - GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); - } - - @Override - public int uploadBuffer(FloatBuffer buf) { - return uploadBuffer(buf, Float.SIZE / Byte.SIZE); - } - - @Override - public int uploadBuffer(ByteBuffer buf) { - return uploadBuffer(buf, 1); - } - - private int uploadBuffer(Buffer buf, int elementSize) { - int[] bufferIds = new int[1]; - mGLId.glGenBuffers(bufferIds.length, bufferIds, 0); - int bufferId = bufferIds[0]; - mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); - mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf, - GL11.GL_STATIC_DRAW); - return bufferId; - } - - @Override - public void recoverFromLightCycle() { - // This is only required for GLES20 - } - - @Override - public void getBounds(Rect bounds, int x, int y, int width, int height) { - // This is only required for GLES20 - } - - @Override - public GLId getGLId() { - return mGLId; - } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLES11IdImpl.java b/app/src/main/java/com/hippo/glview/glrenderer/GLES11IdImpl.java index 05e9388a0..06713cdd2 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLES11IdImpl.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLES11IdImpl.java @@ -24,9 +24,9 @@ import javax.microedition.khronos.opengles.GL11ExtensionPack; */ public class GLES11IdImpl implements GLId { - private static int sNextId = 1; // Mutex for sNextId private final static Object sLock = new Object(); + private static int sNextId = 1; @Override public int generateTexture() { diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java b/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java index 3790688ff..a0ff048b7 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java @@ -23,8 +23,10 @@ import android.opengl.GLES20; import android.opengl.GLUtils; import android.opengl.Matrix; import android.util.Log; + import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.collect.IntList; + import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -54,6 +56,73 @@ public class GLES20Canvas implements GLCanvas { private static final int OFFSET_LAST = OFFSET_DRAW_CIRCLE + COUNT_CIRCLE_VERTEX + 1; private static final float[] BOX_COORDINATES = new float[OFFSET_LAST * 2]; + private static final float[] BOUNDS_COORDINATES = { + 0, 0, 0, 1, + 1, 1, 0, 1, + }; + private static final String POSITION_ATTRIBUTE = "aPosition"; + private static final String COLOR_UNIFORM = "uColor"; + private static final String MATRIX_UNIFORM = "uMatrix"; + private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; + private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; + private static final String ALPHA_UNIFORM = "uAlpha"; + private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; + private static final String DRAW_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + "}\n"; + private static final String DRAW_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "uniform vec4 " + COLOR_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = " + COLOR_UNIFORM + ";\n" + + "}\n"; + private static final String TEXTURE_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" + + "}\n"; + private static final String MESH_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "}\n"; + private static final String TEXTURE_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + private static final int INITIAL_RESTORE_STATE_SIZE = 8; + private static final int MATRIX_SIZE = 16; + // Handle indices -- common + private static final int INDEX_POSITION = 0; + private static final int INDEX_MATRIX = 1; + // Handle indices -- draw + private static final int INDEX_COLOR = 2; + // Handle indices -- texture + private static final int INDEX_TEXTURE_MATRIX = 2; + private static final int INDEX_TEXTURE_SAMPLER = 3; + private static final int INDEX_ALPHA = 4; + // Handle indices -- mesh + private static final int INDEX_TEXTURE_COORD = 2; + private static final GLId mGLId = new GLES20IdImpl(); static { float[] temp = { @@ -88,88 +157,9 @@ public class GLES20Canvas implements GLCanvas { } } - private static final float[] BOUNDS_COORDINATES = { - 0, 0, 0, 1, - 1, 1, 0, 1, - }; - - private static final String POSITION_ATTRIBUTE = "aPosition"; - private static final String COLOR_UNIFORM = "uColor"; - private static final String MATRIX_UNIFORM = "uMatrix"; - private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; - private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; - private static final String ALPHA_UNIFORM = "uAlpha"; - private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; - - private static final String DRAW_VERTEX_SHADER = "" - + "uniform mat4 " + MATRIX_UNIFORM + ";\n" - + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" - + "void main() {\n" - + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" - + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" - + "}\n"; - - private static final String DRAW_FRAGMENT_SHADER = "" - + "precision mediump float;\n" - + "uniform vec4 " + COLOR_UNIFORM + ";\n" - + "void main() {\n" - + " gl_FragColor = " + COLOR_UNIFORM + ";\n" - + "}\n"; - - private static final String TEXTURE_VERTEX_SHADER = "" - + "uniform mat4 " + MATRIX_UNIFORM + ";\n" - + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" - + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" - + "varying vec2 vTextureCoord;\n" - + "void main() {\n" - + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" - + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" - + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" - + "}\n"; - - private static final String MESH_VERTEX_SHADER = "" - + "uniform mat4 " + MATRIX_UNIFORM + ";\n" - + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" - + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" - + "varying vec2 vTextureCoord;\n" - + "void main() {\n" - + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" - + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" - + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" - + "}\n"; - - private static final String TEXTURE_FRAGMENT_SHADER = "" - + "precision mediump float;\n" - + "varying vec2 vTextureCoord;\n" - + "uniform float " + ALPHA_UNIFORM + ";\n" - + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" - + "void main() {\n" - + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" - + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" - + "}\n"; - - private static final int INITIAL_RESTORE_STATE_SIZE = 8; - private static final int MATRIX_SIZE = 16; - - // Keep track of restore state - private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; - private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; private final IntList mSaveFlags = new IntList(); - - private int mCurrentAlphaIndex = 0; - private int mCurrentMatrixIndex = 0; - - // Viewport size - private int mWidth; - private int mHeight; - // Projection matrix private final float[] mProjectionMatrix = new float[MATRIX_SIZE]; - - // Screen size for when we aren't bound to a texture - private int mScreenWidth; - private int mScreenHeight; - // GL programs private final int mDrawProgram; private final int mTextureProgram; @@ -177,57 +167,20 @@ public class GLES20Canvas implements GLCanvas { // GL buffer containing BOX_COORDINATES private final int mBoxCoordinates; - - // Handle indices -- common - private static final int INDEX_POSITION = 0; - private static final int INDEX_MATRIX = 1; - - // Handle indices -- draw - private static final int INDEX_COLOR = 2; - - // Handle indices -- texture - private static final int INDEX_TEXTURE_MATRIX = 2; - private static final int INDEX_TEXTURE_SAMPLER = 3; - private static final int INDEX_ALPHA = 4; - - // Handle indices -- mesh - private static final int INDEX_TEXTURE_COORD = 2; - - private abstract static class ShaderParameter { - public int handle; - protected final String mName; - - public ShaderParameter(String name) { - mName = name; - } - - public abstract void loadHandle(int program); - } - - private static class UniformShaderParameter extends ShaderParameter { - public UniformShaderParameter(String name) { - super(name); - } - - @Override - public void loadHandle(int program) { - handle = GLES20.glGetUniformLocation(program, mName); - checkError(); - } - } - - private static class AttributeShaderParameter extends ShaderParameter { - public AttributeShaderParameter(String name) { - super(name); - } - - @Override - public void loadHandle(int program) { - handle = GLES20.glGetAttribLocation(program, mName); - checkError(); - } - } - + private final IntList mUnboundTextures = new IntList(); + private final IntList mDeleteBuffers = new IntList(); + // Buffer for framebuffer IDs -- we keep track so we can switch the attached + // texture. + private final int[] mFrameBuffer = new int[1]; + // Bound textures. + private final ArrayList mTargetTextures = new ArrayList<>(); + // Temporary variables used within calculations + private final float[] mTempMatrix = new float[32]; + private final float[] mTempColor = new float[4]; + private final RectF mTempSourceRect = new RectF(); + private final RectF mTempTargetRect = new RectF(); + private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; + private final int[] mTempIntArray = new int[1]; ShaderParameter[] mDrawParameters = { new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX @@ -247,33 +200,22 @@ public class GLES20Canvas implements GLCanvas { new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA }; - - private final IntList mUnboundTextures = new IntList(); - private final IntList mDeleteBuffers = new IntList(); - + // Keep track of restore state + private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; + private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; + private int mCurrentAlphaIndex = 0; + private int mCurrentMatrixIndex = 0; + // Viewport size + private int mWidth; + private int mHeight; + // Screen size for when we aren't bound to a texture + private int mScreenWidth; + private int mScreenHeight; // Keep track of statistics for debugging private int mCountDrawMesh = 0; private int mCountTextureRect = 0; private int mCountFillRect = 0; private int mCountDrawLine = 0; - - // Buffer for framebuffer IDs -- we keep track so we can switch the attached - // texture. - private final int[] mFrameBuffer = new int[1]; - - // Bound textures. - private final ArrayList mTargetTextures = new ArrayList<>(); - - // Temporary variables used within calculations - private final float[] mTempMatrix = new float[32]; - private final float[] mTempColor = new float[4]; - private final RectF mTempSourceRect = new RectF(); - private final RectF mTempTargetRect = new RectF(); - private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; - private final int[] mTempIntArray = new int[1]; - - private static final GLId mGLId = new GLES20IdImpl(); - public GLES20Canvas() { Matrix.setIdentityM(mTempTextureMatrix, 0); Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); @@ -307,6 +249,104 @@ public class GLES20Canvas implements GLCanvas { return buffer; } + private static int loadShader(int type, String shaderCode) { + // create a vertex shader type (GLES20.GL_VERTEX_SHADER) + // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) + int shader = GLES20.glCreateShader(type); + + // add the source code to the shader and compile it + GLES20.glShaderSource(shader, shaderCode); + checkError(); + GLES20.glCompileShader(shader); + checkError(); + + return shader; + } + + private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { + int left = 0; + int top = 0; + int right = texture.getWidth(); + int bottom = texture.getHeight(); + if (texture.hasBorder()) { + left = 1; + top = 1; + right -= 1; + bottom -= 1; + } + outRect.set(left, top, right, bottom); + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + private static void checkFramebufferStatus() { + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + String msg = ""; + switch (status) { + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: + msg = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + + public static void checkError() { + int error = GLES20.glGetError(); + if (error != 0) { + Throwable t = new Throwable(); + Log.e(TAG, "GL error: " + error, t); + } + } + + @SuppressWarnings("unused") + private static void printMatrix(String message, float[] m, int offset) { + StringBuilder b = new StringBuilder(message); + for (int i = 0; i < MATRIX_SIZE; i++) { + b.append(' '); + if (i % 4 == 0) { + b.append('\n'); + } + b.append(m[offset + i]); + } + Log.v(TAG, b.toString()); + } + private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) { int program = GLES20.glCreateProgram(); checkError(); @@ -333,20 +373,6 @@ public class GLES20Canvas implements GLCanvas { return program; } - private static int loadShader(int type, String shaderCode) { - // create a vertex shader type (GLES20.GL_VERTEX_SHADER) - // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) - int shader = GLES20.glCreateShader(type); - - // add the source code to the shader and compile it - GLES20.glShaderSource(shader, shaderCode); - checkError(); - GLES20.glCompileShader(shader); - checkError(); - - return shader; - } - @Override public void setSize(int width, int height) { mWidth = width; @@ -502,19 +528,19 @@ public class GLES20Canvas implements GLCanvas { @Override public void drawArc(float cx, float cy, float radiusX, float radiusY, - float sweepAngle, GLPaint paint) { + float sweepAngle, GLPaint paint) { float halfLineWidth = paint.getLineWidth() / 2; fillSector(cx, cy, radiusX + halfLineWidth, radiusY + halfLineWidth, sweepAngle, paint.getColor()); fillOval(cx, cy, radiusX - halfLineWidth, radiusY - halfLineWidth, paint.getBackgroundColor()); } private void draw(int type, int offset, int count, float x, float y, float width, float height, - GLPaint paint) { + GLPaint paint) { draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth()); } private void draw(int type, int offset, int count, float x, float y, float width, float height, - int color, float lineWidth) { + int color, float lineWidth) { prepareDraw(offset, color, lineWidth); draw(mDrawParameters, type, count, x, y, width, height); } @@ -572,7 +598,7 @@ public class GLES20Canvas implements GLCanvas { } private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width, - float height) { + float height) { setMatrix(params, x, y, width, height); int positionHandle = params[INDEX_POSITION].handle; GLES20.glEnableVertexAttribArray(positionHandle); @@ -630,20 +656,6 @@ public class GLES20Canvas implements GLCanvas { drawTextureRect(texture, mTempSourceRect, mTempTargetRect); } - private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { - int left = 0; - int top = 0; - int right = texture.getWidth(); - int bottom = texture.getHeight(); - if (texture.hasBorder()) { - left = 1; - top = 1; - right -= 1; - bottom -= 1; - } - outRect.set(left, top, right, bottom); - } - @Override public void drawTexture(BasicTexture texture, RectF source, RectF target) { if (target.width() <= 0 || target.height() <= 0) { @@ -658,7 +670,7 @@ public class GLES20Canvas implements GLCanvas { @Override public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w, - int h) { + int h) { if (w <= 0 || h <= 0) { return; } @@ -678,33 +690,6 @@ public class GLES20Canvas implements GLCanvas { mTempTextureMatrix[13] = source.top; } - // This function changes the source coordinate to the texture coordinates. - // It also clips the source and target coordinates if it is beyond the - // bound of the texture. - private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { - int width = texture.getWidth(); - int height = texture.getHeight(); - int texWidth = texture.getTextureWidth(); - int texHeight = texture.getTextureHeight(); - // Convert to texture coordinates - source.left /= texWidth; - source.right /= texWidth; - source.top /= texHeight; - source.bottom /= texHeight; - - // Clip if the rendering range is beyond the bound of the texture. - float xBound = (float) width / texWidth; - if (source.right > xBound) { - target.right = target.left + target.width() * (xBound - source.left) / source.width(); - source.right = xBound; - } - float yBound = (float) height / texHeight; - if (source.bottom > yBound) { - target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); - source.bottom = yBound; - } - } - private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) { ShaderParameter[] params = mTextureParameters; prepareTexture(texture, mTextureProgram, params); @@ -742,7 +727,7 @@ public class GLES20Canvas implements GLCanvas { @Override public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer, - int indexBuffer, int indexCount) { + int indexBuffer, int indexCount) { prepareTexture(texture, mMeshProgram, mMeshParameters); GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); @@ -908,28 +893,6 @@ public class GLES20Canvas implements GLCanvas { } } - private static void checkFramebufferStatus() { - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { - String msg = ""; - switch (status) { - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; - break; - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; - break; - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; - break; - case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: - msg = "GL_FRAMEBUFFER_UNSUPPORTED"; - break; - } - throw new RuntimeException(msg + ":" + Integer.toHexString(status)); - } - } - @Override public void setTextureParameters(BasicTexture texture) { int target = texture.getTarget(); @@ -961,7 +924,7 @@ public class GLES20Canvas implements GLCanvas { @Override public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, - int format, int type) { + int format, int type) { int target = texture.getTarget(); GLES20.glBindTexture(target, texture.getId()); checkError(); @@ -990,27 +953,6 @@ public class GLES20Canvas implements GLCanvas { return bufferId; } - public static void checkError() { - int error = GLES20.glGetError(); - if (error != 0) { - Throwable t = new Throwable(); - Log.e(TAG, "GL error: " + error, t); - } - } - - @SuppressWarnings("unused") - private static void printMatrix(String message, float[] m, int offset) { - StringBuilder b = new StringBuilder(message); - for (int i = 0; i < MATRIX_SIZE; i++) { - b.append(' '); - if (i % 4 == 0) { - b.append('\n'); - } - b.append(m[offset + i]); - } - Log.v(TAG, b.toString()); - } - @Override public void recoverFromLightCycle() { GLES20.glViewport(0, 0, mWidth, mHeight); @@ -1036,4 +978,39 @@ public class GLES20Canvas implements GLCanvas { public GLId getGLId() { return mGLId; } + + private abstract static class ShaderParameter { + protected final String mName; + public int handle; + + public ShaderParameter(String name) { + mName = name; + } + + public abstract void loadHandle(int program); + } + + private static class UniformShaderParameter extends ShaderParameter { + public UniformShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetUniformLocation(program, mName); + checkError(); + } + } + + private static class AttributeShaderParameter extends ShaderParameter { + public AttributeShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetAttribLocation(program, mName); + checkError(); + } + } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLPaint.java b/app/src/main/java/com/hippo/glview/glrenderer/GLPaint.java index 2f189997b..75044387a 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLPaint.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLPaint.java @@ -21,20 +21,24 @@ public class GLPaint { private int mColor = 0; private int mBackgroundColor = 0; + public int getColor() { + return mColor; + } + public void setColor(int color) { mColor = color; } - public int getColor() { - return mColor; + public int getBackgroundColor() { + return mBackgroundColor; } public void setBackgroundColor(int backgroundColor) { mBackgroundColor = backgroundColor; } - public int getBackgroundColor() { - return mBackgroundColor; + public float getLineWidth() { + return mLineWidth; } public void setLineWidth(float width) { @@ -43,8 +47,4 @@ public class GLPaint { } mLineWidth = width; } - - public float getLineWidth() { - return mLineWidth; - } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/MovableTextTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/MovableTextTexture.java index 916a36827..7474a7c97 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/MovableTextTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/MovableTextTexture.java @@ -8,6 +8,7 @@ import android.graphics.Typeface; import java.util.Arrays; // TODO support multiline + /** * Works like movable type.
*
@@ -21,7 +22,7 @@ public final class MovableTextTexture extends SpriteTexture { private final float mMaxWidth; private MovableTextTexture(Bitmap bitmap, int count, int[] rects, char[] characters, - float[] widths, float height, float maxWidth) { + float[] widths, float height, float maxWidth) { super(bitmap, false, count, rects); mCharacters = characters; mWidths = widths; @@ -29,6 +30,73 @@ public final class MovableTextTexture extends SpriteTexture { mMaxWidth = maxWidth; } + /** + * Create a TextTexture to draw text + * + * @param typeface the typeface + * @param size text size + * @param characters all Characters + * @return the TextTexture + */ + public static MovableTextTexture create(Typeface typeface, int size, int color, char[] characters) { + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextSize(size); + paint.setColor(color); + paint.setTypeface(typeface); + + Paint.FontMetricsInt fmi = paint.getFontMetricsInt(); + int fixed = fmi.bottom; + int height = fmi.bottom - fmi.top; + + int length = characters.length; + float[] widths = new float[length]; + paint.getTextWidths(characters, 0, length, widths); + + // Calculate bitmap size + float maxWidth = 0.0f; + for (float f : widths) { + maxWidth = Math.max(maxWidth, f); + } + int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length)); + int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length)); + if (hCount * (vCount - 1) > length) { + vCount--; + } + if ((hCount - 1) * vCount > length) { + hCount--; + } + + Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth), + (int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.translate(0, height - fixed); + + // Draw + int[] rects = new int[length * 4]; + int x = 0; + int y = 0; + for (int i = 0; i < length; i++) { + int offset = i * 4; + rects[offset + 0] = x; + rects[offset + 1] = y; + rects[offset + 2] = (int) widths[i]; + rects[offset + 3] = height; + + canvas.drawText(characters, i, 1, x, y, paint); + + if (i % hCount == hCount - 1) { + // The end of row + x = 0; + y += height; + } else { + x += maxWidth; + } + } + + return new MovableTextTexture(bitmap, length, rects, characters, widths, height, maxWidth); + } + public int[] getTextIndexes(String text) { char[] characters = mCharacters; @@ -113,71 +181,4 @@ public final class MovableTextTexture extends SpriteTexture { } } } - - /** - * Create a TextTexture to draw text - * - * @param typeface the typeface - * @param size text size - * @param characters all Characters - * @return the TextTexture - */ - public static MovableTextTexture create(Typeface typeface, int size, int color, char[] characters) { - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextSize(size); - paint.setColor(color); - paint.setTypeface(typeface); - - Paint.FontMetricsInt fmi = paint.getFontMetricsInt(); - int fixed = fmi.bottom; - int height = fmi.bottom - fmi.top; - - int length = characters.length; - float[] widths = new float[length]; - paint.getTextWidths(characters, 0, length, widths); - - // Calculate bitmap size - float maxWidth = 0.0f; - for (float f : widths) { - maxWidth = Math.max(maxWidth, f); - } - int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length)); - int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length)); - if (hCount * (vCount - 1) > length) { - vCount--; - } - if ((hCount - 1) * vCount > length) { - hCount--; - } - - Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth), - (int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - canvas.translate(0, height - fixed); - - // Draw - int[] rects = new int[length * 4]; - int x = 0; - int y = 0; - for (int i = 0; i < length; i++) { - int offset = i * 4; - rects[offset + 0] = x; - rects[offset + 1] = y; - rects[offset + 2] = (int) widths[i]; - rects[offset + 3] = height; - - canvas.drawText(characters, i, 1, x, y, paint); - - if (i % hCount == hCount - 1) { - // The end of row - x = 0; - y += height; - } else { - x += maxWidth; - } - } - - return new MovableTextTexture(bitmap, length, rects, characters, widths, height, maxWidth); - } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/NativeTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/NativeTexture.java index e44a1dc2e..beea42c69 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/NativeTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/NativeTexture.java @@ -29,10 +29,6 @@ public abstract class NativeTexture extends BasicTexture { private boolean mOpaque = true; - public void invalidateContent() { - mContentValid = false; - } - public static void checkError() { int error = GLES20.glGetError(); if (error != 0) { @@ -41,6 +37,10 @@ public abstract class NativeTexture extends BasicTexture { } } + public void invalidateContent() { + mContentValid = false; + } + protected abstract void texImage(boolean init); private void uploadToCanvas(GLCanvas canvas) { @@ -92,12 +92,12 @@ public abstract class NativeTexture extends BasicTexture { return GL11.GL_TEXTURE_2D; } - public void setOpaque(boolean isOpaque) { - mOpaque = isOpaque; - } - @Override public boolean isOpaque() { return mOpaque; } + + public void setOpaque(boolean isOpaque) { + mOpaque = isOpaque; + } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/NinePatchTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/NinePatchTexture.java index bd5e1de25..ecb43017a 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/NinePatchTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/NinePatchTexture.java @@ -74,6 +74,46 @@ public class NinePatchTexture extends ResourceTexture { return mChunk; } + private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) { + int key = w; + key = (key << 16) | h; + NinePatchInstance instance = mInstanceCache.get(key); + + if (instance == null) { + instance = new NinePatchInstance(this, w, h); + NinePatchInstance removed = mInstanceCache.put(key, instance); + if (removed != null) { + removed.recycle(canvas); + } + } + + return instance; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + if (!isLoaded()) { + mInstanceCache.clear(); + } + + if (w != 0 && h != 0) { + findInstance(canvas, w, h).draw(canvas, this, x, y); + } + } + + @Override + public void recycle() { + super.recycle(); + GLCanvas canvas = mCanvasRef; + if (canvas == null) return; + int n = mInstanceCache.size(); + for (int i = 0; i < n; i++) { + NinePatchInstance instance = mInstanceCache.valueAt(i); + instance.recycle(canvas); + } + mInstanceCache.clear(); + } + // This is a simple cache for a small number of things. Linear search // is used because the cache is small. It also tries to remove less used // item when the cache is full by moving the often-used items to the front. @@ -136,46 +176,6 @@ public class NinePatchTexture extends ResourceTexture { return mValue[i]; } } - - private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) { - int key = w; - key = (key << 16) | h; - NinePatchInstance instance = mInstanceCache.get(key); - - if (instance == null) { - instance = new NinePatchInstance(this, w, h); - NinePatchInstance removed = mInstanceCache.put(key, instance); - if (removed != null) { - removed.recycle(canvas); - } - } - - return instance; - } - - @Override - public void draw(GLCanvas canvas, int x, int y, int w, int h) { - if (!isLoaded()) { - mInstanceCache.clear(); - } - - if (w != 0 && h != 0) { - findInstance(canvas, w, h).draw(canvas, this, x, y); - } - } - - @Override - public void recycle() { - super.recycle(); - GLCanvas canvas = mCanvasRef; - if (canvas == null) return; - int n = mInstanceCache.size(); - for (int i = 0; i < n; i++) { - NinePatchInstance instance = mInstanceCache.valueAt(i); - instance.recycle(canvas); - } - mInstanceCache.clear(); - } } // This keeps data for a specialization of NinePatchTexture with the size @@ -251,13 +251,13 @@ class NinePatchInstance { * s: stretchy segment * * - * @param div the stretch parts defined in nine-patch chunk + * @param div the stretch parts defined in nine-patch chunk * @param source the length of the texture * @param target the length on the drawing plan - * @param u output, the positions of these dividers in the texture - * coordinate - * @param x output, the corresponding position of these dividers on the - * drawing plan + * @param u output, the positions of these dividers in the texture + * coordinate + * @param x output, the corresponding position of these dividers on the + * drawing plan * @return the number of these dividers. */ private static int stretch( @@ -293,7 +293,7 @@ class NinePatchInstance { lastX = x[i + 1] + partX; lastU = div[i + 1]; x[i + 2] = lastX - 0.5f; - u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound); + u[i + 2] = Math.min((lastU - 0.5f) / textureSize, textureBound); } // the last fixed segment x[div.length + 1] = target; @@ -309,8 +309,12 @@ class NinePatchInstance { return last + 1; } + private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + private void prepareVertexData(float x[], float y[], float u[], float v[], - int nx, int ny, int[] color) { + int nx, int ny, int[] color) { /* * Given a 3x3 nine-patch image, the vertex order is defined as the * following graph: @@ -391,10 +395,6 @@ class NinePatchInstance { mIndexBuffer.put(index, 0, idxCount).position(0); } - private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { - return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); - } - private void prepareBuffers(GLCanvas canvas) { mXyBufferName = canvas.uploadBuffer(mXyBuffer); mUvBufferName = canvas.uploadBuffer(mUvBuffer); diff --git a/app/src/main/java/com/hippo/glview/glrenderer/RawTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/RawTexture.java index 0a5f7f2e9..d54b42a1c 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/RawTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/RawTexture.java @@ -62,9 +62,9 @@ public class RawTexture extends BasicTexture { } @Override - public void yield() { - // we cannot free the texture because we have no backup. - } + public void yield() { + // we cannot free the texture because we have no backup. + } @Override protected int getTarget() { diff --git a/app/src/main/java/com/hippo/glview/glrenderer/SpriteTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/SpriteTexture.java index c34acb786..442e45cb9 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/SpriteTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/SpriteTexture.java @@ -60,7 +60,7 @@ public class SpriteTexture extends TiledTexture { } public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio, - int x, int y, int width, int height) { + int x, int y, int width, int height) { int[] rects = mRects; int offset = index * 4; int sourceX = rects[offset]; diff --git a/app/src/main/java/com/hippo/glview/glrenderer/StringTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/StringTexture.java index fc02ffeda..a898a2f28 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/StringTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/StringTexture.java @@ -34,7 +34,7 @@ public class StringTexture extends CanvasTexture { private final FontMetricsInt mMetrics; private StringTexture(String text, TextPaint paint, - FontMetricsInt metrics, int width, int height) { + FontMetricsInt metrics, int width, int height) { super(width, height); mText = text; mPaint = paint; diff --git a/app/src/main/java/com/hippo/glview/glrenderer/Texture.java b/app/src/main/java/com/hippo/glview/glrenderer/Texture.java index c77c751aa..91df4239b 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/Texture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/Texture.java @@ -39,9 +39,14 @@ import android.graphics.RectF; // public interface Texture { int getWidth(); + int getHeight(); + void draw(GLCanvas canvas, int x, int y); + void draw(GLCanvas canvas, int x, int y, int w, int h); + void draw(GLCanvas canvas, RectF source, RectF target); + boolean isOpaque(); } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/TextureUploader.java b/app/src/main/java/com/hippo/glview/glrenderer/TextureUploader.java index b2f8818a8..7cf83719c 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/TextureUploader.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/TextureUploader.java @@ -66,7 +66,7 @@ public class TextureUploader implements GLRoot.OnGLIdleListener { } private int upload(GLCanvas canvas, ArrayDeque deque, - int uploadQuota, boolean isBackground) { + int uploadQuota, boolean isBackground) { while (uploadQuota > 0) { UploadedTexture t; synchronized (this) { diff --git a/app/src/main/java/com/hippo/glview/glrenderer/TiledTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/TiledTexture.java index daacbbe38..bc8794e6a 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/TiledTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/TiledTexture.java @@ -43,23 +43,12 @@ public class TiledTexture implements Texture { // We are targeting at 60fps, so we have 16ms for each frame. // In this 16ms, we use about 4~8 ms to upload tiles. private static final long UPLOAD_TILE_LIMIT = 4; // ms - - private static Tile sFreeTileHead = null; private static final Object sFreeTileLock = new Object(); - private static final Bitmap sUploadBitmap; private static final Canvas sCanvas; private static final Paint sBitmapPaint; private static final Paint sPaint; - - private int mUploadIndex = 0; - - private final Tile[] mTiles; // Can be modified in different threads. - // Should be protected by "synchronized." - private final int mWidth; - private final int mHeight; - private final RectF mSrcRect = new RectF(); - private final RectF mDestRect = new RectF(); + private static Tile sFreeTileHead = null; static { sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888); @@ -71,6 +60,219 @@ public class TiledTexture implements Texture { sPaint.setColor(Color.TRANSPARENT); } + private final Tile[] mTiles; // Can be modified in different threads. + // Should be protected by "synchronized." + private final int mWidth; + private final int mHeight; + private final RectF mSrcRect = new RectF(); + private final RectF mDestRect = new RectF(); + private int mUploadIndex = 0; + + public TiledTexture(Bitmap bitmap, boolean isOpaque) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + ArrayList list = new ArrayList<>(); + + for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { + for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { + Tile tile = obtainTile(); + tile.offsetX = x; + tile.offsetY = y; + tile.bitmap = bitmap; + tile.setSize( + Math.min(CONTENT_SIZE, mWidth - x), + Math.min(CONTENT_SIZE, mHeight - y)); + tile.setOpaque(isOpaque); + list.add(tile); + } + } + mTiles = list.toArray(new Tile[list.size()]); + } + + private static void freeTile(Tile tile) { + tile.invalidateContent(); + tile.bitmap = null; + synchronized (sFreeTileLock) { + tile.nextFreeTile = sFreeTileHead; + sFreeTileHead = tile; + } + } + + private static Tile obtainTile() { + synchronized (sFreeTileLock) { + Tile result = sFreeTileHead; + if (result == null) return new Tile(); + sFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + return result; + } + } + + // We want to draw the "source" on the "target". + // This method is to find the "output" rectangle which is + // the corresponding area of the "src". + // (x,y) target + // (x0,y0) source +---------------+ + // +----------+ | | + // | src | | output | + // | +--+ | linear map | +----+ | + // | +--+ | ----------> | | | | + // | | by (scaleX, scaleY) | +----+ | + // +----------+ | | + // Texture +---------------+ + // Canvas + private static void mapRect(RectF output, + RectF src, float x0, float y0, float x, float y, float scaleX, + float scaleY) { + output.set(x + (src.left - x0) * scaleX, + y + (src.top - y0) * scaleY, + x + (src.right - x0) * scaleX, + y + (src.bottom - y0) * scaleY); + } + + private boolean uploadNextTile(GLCanvas canvas) { + if (mUploadIndex == mTiles.length) return true; + + synchronized (mTiles) { + Tile next = mTiles[mUploadIndex++]; + + // Make sure tile has not already been recycled by the time + // this is called (race condition in onGLIdle) + if (next.bitmap != null) { + boolean hasBeenLoad = next.isLoaded(); + next.updateContent(canvas); + + // It will take some time for a texture to be drawn for the first + // time. When scrolling, we need to draw several tiles on the screen + // at the same time. It may cause a UI jank even these textures has + // been uploaded. + if (!hasBeenLoad) next.draw(canvas, 0, 0); + } + } + return mUploadIndex == mTiles.length; + } + + public boolean isReady() { + return mUploadIndex == mTiles.length; + } + + // Can be called in UI thread. + public void recycle() { + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + freeTile(mTiles[i]); + } + } + } + + // Draws a mixed color of this texture and a specified color onto the + // a rectangle. The used color is: from * (1 - ratio) + to * ratio. + public void drawMixed(GLCanvas canvas, int color, float ratio, + int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth; + float scaleY = (float) height / mHeight; + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); + } + } + } + + public void drawMixed(GLCanvas canvas, int color, float ratio, + RectF source, RectF target) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float x0 = source.left; + float y0 = source.top; + float x = target.left; + float y = target.top; + float scaleX = target.width() / source.width(); + float scaleY = target.height() / source.height(); + + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) continue; + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); + } + } + } + + // Draws the texture on to the specified rectangle. + @Override + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth; + float scaleY = (float) height / mHeight; + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, mSrcRect, mDestRect); + } + } + } + + // Draws a sub region of this texture on to the specified rectangle. + @Override + public void draw(GLCanvas canvas, RectF source, RectF target) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float x0 = source.left; + float y0 = source.top; + float x = target.left; + float y = target.top; + float scaleX = target.width() / source.width(); + float scaleY = target.height() / source.height(); + + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) continue; + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, src, dest); + } + } + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public void draw(GLCanvas canvas, int x, int y) { + draw(canvas, x, y, mWidth, mHeight); + } + + @Override + public boolean isOpaque() { + return false; + } + public static class Uploader implements GLRoot.OnGLIdleListener { private final ArrayDeque mTextures = new ArrayDeque<>(INIT_CAPACITY); @@ -165,209 +367,4 @@ public class TiledTexture implements Texture { // do nothing } } - - private static void freeTile(Tile tile) { - tile.invalidateContent(); - tile.bitmap = null; - synchronized (sFreeTileLock) { - tile.nextFreeTile = sFreeTileHead; - sFreeTileHead = tile; - } - } - - private static Tile obtainTile() { - synchronized (sFreeTileLock) { - Tile result = sFreeTileHead; - if (result == null) return new Tile(); - sFreeTileHead = result.nextFreeTile; - result.nextFreeTile = null; - return result; - } - } - - private boolean uploadNextTile(GLCanvas canvas) { - if (mUploadIndex == mTiles.length) return true; - - synchronized (mTiles) { - Tile next = mTiles[mUploadIndex++]; - - // Make sure tile has not already been recycled by the time - // this is called (race condition in onGLIdle) - if (next.bitmap != null) { - boolean hasBeenLoad = next.isLoaded(); - next.updateContent(canvas); - - // It will take some time for a texture to be drawn for the first - // time. When scrolling, we need to draw several tiles on the screen - // at the same time. It may cause a UI jank even these textures has - // been uploaded. - if (!hasBeenLoad) next.draw(canvas, 0, 0); - } - } - return mUploadIndex == mTiles.length; - } - - public TiledTexture(Bitmap bitmap, boolean isOpaque) { - mWidth = bitmap.getWidth(); - mHeight = bitmap.getHeight(); - ArrayList list = new ArrayList<>(); - - for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { - for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { - Tile tile = obtainTile(); - tile.offsetX = x; - tile.offsetY = y; - tile.bitmap = bitmap; - tile.setSize( - Math.min(CONTENT_SIZE, mWidth - x), - Math.min(CONTENT_SIZE, mHeight - y)); - tile.setOpaque(isOpaque); - list.add(tile); - } - } - mTiles = list.toArray(new Tile[list.size()]); - } - - public boolean isReady() { - return mUploadIndex == mTiles.length; - } - - // Can be called in UI thread. - public void recycle() { - synchronized (mTiles) { - for (int i = 0, n = mTiles.length; i < n; ++i) { - freeTile(mTiles[i]); - } - } - } - - // We want to draw the "source" on the "target". - // This method is to find the "output" rectangle which is - // the corresponding area of the "src". - // (x,y) target - // (x0,y0) source +---------------+ - // +----------+ | | - // | src | | output | - // | +--+ | linear map | +----+ | - // | +--+ | ----------> | | | | - // | | by (scaleX, scaleY) | +----+ | - // +----------+ | | - // Texture +---------------+ - // Canvas - private static void mapRect(RectF output, - RectF src, float x0, float y0, float x, float y, float scaleX, - float scaleY) { - output.set(x + (src.left - x0) * scaleX, - y + (src.top - y0) * scaleY, - x + (src.right - x0) * scaleX, - y + (src.bottom - y0) * scaleY); - } - - // Draws a mixed color of this texture and a specified color onto the - // a rectangle. The used color is: from * (1 - ratio) + to * ratio. - public void drawMixed(GLCanvas canvas, int color, float ratio, - int x, int y, int width, int height) { - RectF src = mSrcRect; - RectF dest = mDestRect; - float scaleX = (float) width / mWidth; - float scaleY = (float) height / mHeight; - synchronized (mTiles) { - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); - src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); - canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); - } - } - } - - public void drawMixed(GLCanvas canvas, int color, float ratio, - RectF source, RectF target) { - RectF src = mSrcRect; - RectF dest = mDestRect; - float x0 = source.left; - float y0 = source.top; - float x = target.left; - float y = target.top; - float scaleX = target.width() / source.width(); - float scaleY = target.height() / source.height(); - - synchronized (mTiles) { - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - if (!src.intersect(source)) continue; - mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); - src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); - canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); - } - } - } - - // Draws the texture on to the specified rectangle. - @Override - public void draw(GLCanvas canvas, int x, int y, int width, int height) { - RectF src = mSrcRect; - RectF dest = mDestRect; - float scaleX = (float) width / mWidth; - float scaleY = (float) height / mHeight; - synchronized (mTiles) { - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); - src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); - canvas.drawTexture(t, mSrcRect, mDestRect); - } - } - } - - // Draws a sub region of this texture on to the specified rectangle. - @Override - public void draw(GLCanvas canvas, RectF source, RectF target) { - RectF src = mSrcRect; - RectF dest = mDestRect; - float x0 = source.left; - float y0 = source.top; - float x = target.left; - float y = target.top; - float scaleX = target.width() / source.width(); - float scaleY = target.height() / source.height(); - - synchronized (mTiles) { - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - if (!src.intersect(source)) continue; - mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); - src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); - canvas.drawTexture(t, src, dest); - } - } - } - - @Override - public int getWidth() { - return mWidth; - } - - @Override - public int getHeight() { - return mHeight; - } - - @Override - public void draw(GLCanvas canvas, int x, int y) { - draw(canvas, x, y, mWidth, mHeight); - } - - @Override - public boolean isOpaque() { - return false; - } } diff --git a/app/src/main/java/com/hippo/glview/glrenderer/UploadedTexture.java b/app/src/main/java/com/hippo/glview/glrenderer/UploadedTexture.java index ea9a912ee..cf9b2eef8 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/UploadedTexture.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/UploadedTexture.java @@ -48,16 +48,14 @@ public abstract class UploadedTexture extends BasicTexture { @SuppressWarnings("unused") private static final String TAG = "Texture"; + private static final int UPLOAD_LIMIT = 100; + private static int sUploadedCount; + protected Bitmap mBitmap; private boolean mContentValid = true; - // indicate this textures is being uploaded in background private boolean mIsUploading = false; private boolean mOpaque = true; private boolean mThrottled = false; - private static int sUploadedCount; - private static final int UPLOAD_LIMIT = 100; - - protected Bitmap mBitmap; private int mBorder; protected UploadedTexture() { @@ -72,47 +70,6 @@ public abstract class UploadedTexture extends BasicTexture { } } - protected void setIsUploading(boolean uploading) { - mIsUploading = uploading; - } - - public boolean isUploading() { - return mIsUploading; - } - - private static class BorderKey implements Cloneable { - public boolean vertical; - public Config config; - public int length; - - @Override - public int hashCode() { - int x = config.hashCode() ^ length; - return vertical ? x : -x; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof BorderKey)) return false; - BorderKey o = (BorderKey) object; - return vertical == o.vertical - && config == o.config && length == o.length; - } - - @Override - public BorderKey clone() { - try { - return (BorderKey) super.clone(); - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); - } - } - } - - protected void setThrottled(boolean throttled) { - mThrottled = throttled; - } - private static Bitmap getBorderLine( boolean vertical, Config config, int length) { BorderKey key = sBorderKey; @@ -129,6 +86,26 @@ public abstract class UploadedTexture extends BasicTexture { return bitmap; } + public static void resetUploadLimit() { + sUploadedCount = 0; + } + + public static boolean uploadLimitReached() { + return sUploadedCount > UPLOAD_LIMIT; + } + + protected void setIsUploading(boolean uploading) { + mIsUploading = uploading; + } + + public boolean isUploading() { + return mIsUploading; + } + + protected void setThrottled(boolean throttled) { + mThrottled = throttled; + } + private Bitmap getBitmap() { if (mBitmap == null) { mBitmap = onGetBitmap(); @@ -181,6 +158,7 @@ public abstract class UploadedTexture extends BasicTexture { /** * Updates the content on GPU's memory. + * * @param canvas */ public void updateContent(GLCanvas canvas) { @@ -199,14 +177,6 @@ public abstract class UploadedTexture extends BasicTexture { } } - public static void resetUploadLimit() { - sUploadedCount = 0; - } - - public static boolean uploadLimitReached() { - return sUploadedCount > UPLOAD_LIMIT; - } - private void uploadToCanvas(GLCanvas canvas) { Bitmap bitmap = getBitmap(); @@ -281,18 +251,47 @@ public abstract class UploadedTexture extends BasicTexture { return GL11.GL_TEXTURE_2D; } - public void setOpaque(boolean isOpaque) { - mOpaque = isOpaque; - } - @Override public boolean isOpaque() { return mOpaque; } + public void setOpaque(boolean isOpaque) { + mOpaque = isOpaque; + } + @Override public void recycle() { super.recycle(); if (mBitmap != null) freeBitmap(); } + + private static class BorderKey implements Cloneable { + public boolean vertical; + public Config config; + public int length; + + @Override + public int hashCode() { + int x = config.hashCode() ^ length; + return vertical ? x : -x; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof BorderKey)) return false; + BorderKey o = (BorderKey) object; + return vertical == o.vertical + && config == o.config && length == o.length; + } + + @Override + public BorderKey clone() { + try { + return (BorderKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + } } diff --git a/app/src/main/java/com/hippo/glview/image/ImageMovableTextTexture.java b/app/src/main/java/com/hippo/glview/image/ImageMovableTextTexture.java index 07a53901a..898aa3b3c 100644 --- a/app/src/main/java/com/hippo/glview/image/ImageMovableTextTexture.java +++ b/app/src/main/java/com/hippo/glview/image/ImageMovableTextTexture.java @@ -46,6 +46,84 @@ public class ImageMovableTextTexture extends ImageSpriteTexture { mMaxWidth = maxWidth; } + /** + * Create a TextTexture to draw text + * + * @param typeface the typeface + * @param size text size + * @param characters all Characters + * @return the TextTexture + */ + @Nullable + public static ImageMovableTextTexture create(Typeface typeface, int size, int color, char[] characters) { + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextSize(size); + paint.setColor(color); + paint.setTypeface(typeface); + + Paint.FontMetricsInt fmi = paint.getFontMetricsInt(); + int fixed = fmi.bottom; + int height = fmi.bottom - fmi.top; + + int length = characters.length; + float[] widths = new float[length]; + paint.getTextWidths(characters, 0, length, widths); + + // Calculate bitmap size + float maxWidth = 0.0f; + for (float f : widths) { + maxWidth = Math.max(maxWidth, f); + } + int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length)); + int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length)); + if (hCount * (vCount - 1) > length) { + vCount--; + } + if ((hCount - 1) * vCount > length) { + hCount--; + } + + Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth), + (int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.translate(0, height - fixed); + + // Draw + int[] rects = new int[length * 4]; + int x = 0; + int y = 0; + for (int i = 0; i < length; i++) { + int offset = i * 4; + rects[offset + 0] = x; + rects[offset + 1] = y; + rects[offset + 2] = (int) widths[i]; + rects[offset + 3] = height; + + canvas.drawText(characters, i, 1, x, y, paint); + + if (i % hCount == hCount - 1) { + // The end of row + x = 0; + y += height; + } else { + x += maxWidth; + } + } + + Image image = Image.create(bitmap); + bitmap.recycle(); + if (image == null) { + return null; + } + ImageWrapper imageWrapper = new ImageWrapper(image); + if (imageWrapper.obtain()) { + return new ImageMovableTextTexture(imageWrapper, length, rects, characters, widths, height, maxWidth); + } else { + return null; + } + } + public int[] getTextIndexes(String text) { char[] characters = mCharacters; @@ -130,82 +208,4 @@ public class ImageMovableTextTexture extends ImageSpriteTexture { } } } - - /** - * Create a TextTexture to draw text - * - * @param typeface the typeface - * @param size text size - * @param characters all Characters - * @return the TextTexture - */ - @Nullable - public static ImageMovableTextTexture create(Typeface typeface, int size, int color, char[] characters) { - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextSize(size); - paint.setColor(color); - paint.setTypeface(typeface); - - Paint.FontMetricsInt fmi = paint.getFontMetricsInt(); - int fixed = fmi.bottom; - int height = fmi.bottom - fmi.top; - - int length = characters.length; - float[] widths = new float[length]; - paint.getTextWidths(characters, 0, length, widths); - - // Calculate bitmap size - float maxWidth = 0.0f; - for (float f : widths) { - maxWidth = Math.max(maxWidth, f); - } - int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length)); - int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length)); - if (hCount * (vCount - 1) > length) { - vCount--; - } - if ((hCount - 1) * vCount > length) { - hCount--; - } - - Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth), - (int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - canvas.translate(0, height - fixed); - - // Draw - int[] rects = new int[length * 4]; - int x = 0; - int y = 0; - for (int i = 0; i < length; i++) { - int offset = i * 4; - rects[offset + 0] = x; - rects[offset + 1] = y; - rects[offset + 2] = (int) widths[i]; - rects[offset + 3] = height; - - canvas.drawText(characters, i, 1, x, y, paint); - - if (i % hCount == hCount - 1) { - // The end of row - x = 0; - y += height; - } else { - x += maxWidth; - } - } - - Image image = Image.create(bitmap); - bitmap.recycle(); - if (image == null) { - return null; - } - ImageWrapper imageWrapper = new ImageWrapper(image); - if (imageWrapper.obtain()) { - return new ImageMovableTextTexture(imageWrapper, length, rects, characters, widths, height, maxWidth); - } else { - return null; - } - } } diff --git a/app/src/main/java/com/hippo/glview/image/ImageSpriteTexture.java b/app/src/main/java/com/hippo/glview/image/ImageSpriteTexture.java index 5b65d57cb..b66c9d270 100644 --- a/app/src/main/java/com/hippo/glview/image/ImageSpriteTexture.java +++ b/app/src/main/java/com/hippo/glview/image/ImageSpriteTexture.java @@ -78,7 +78,7 @@ public class ImageSpriteTexture extends ImageTexture { } public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio, - int x, int y, int width, int height) { + int x, int y, int width, int height) { int[] rects = mRects; int offset = index * 4; int sourceX = rects[offset]; diff --git a/app/src/main/java/com/hippo/glview/image/ImageTexture.java b/app/src/main/java/com/hippo/glview/image/ImageTexture.java index d44371532..cd2a79232 100644 --- a/app/src/main/java/com/hippo/glview/image/ImageTexture.java +++ b/app/src/main/java/com/hippo/glview/image/ImageTexture.java @@ -32,6 +32,7 @@ import com.hippo.glview.view.GLRoot; import com.hippo.yorozuya.thread.InfiniteThreadExecutor; import com.hippo.yorozuya.thread.PVLock; import com.hippo.yorozuya.thread.PriorityThreadFactory; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; @@ -43,55 +44,23 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ImageTexture implements Texture, Animatable { - @IntDef({TILE_SMALL, TILE_LARGE}) - @Retention(RetentionPolicy.SOURCE) - private @interface TileType {} - private static final int TILE_SMALL = 0; private static final int TILE_LARGE = 1; - private static final int SMALL_CONTENT_SIZE = 254; private static final int SMALL_BORDER_SIZE = 1; private static final int SMALL_TILE_SIZE = SMALL_CONTENT_SIZE + 2 * SMALL_BORDER_SIZE; - private static final int LARGE_CONTENT_SIZE = SMALL_CONTENT_SIZE * 2; private static final int LARGE_BORDER_SIZE = SMALL_BORDER_SIZE * 2; private static final int LARGE_TILE_SIZE = LARGE_CONTENT_SIZE + 2 * LARGE_BORDER_SIZE; - private static final int INIT_CAPACITY = 8; - // We are targeting at 60fps, so we have 16ms for each frame. // In this 16ms, we use about 4~8 ms to upload tiles. private static final long UPLOAD_TILE_LIMIT = 4; // ms - private static final Executor sThreadExecutor; private static final PVLock sPVLock; - + private static final Object sFreeTileLock = new Object(); private static Tile sSmallFreeTileHead = null; private static Tile sLargeFreeTileHead = null; - private static final Object sFreeTileLock = new Object(); - - private final ImageWrapper mImage; - private int mUploadIndex = 0; - private final Tile[] mTiles; // Can be modified in different threads. - // Should be protected by "synchronized." - - private final int mWidth; - private final int mHeight; - private final boolean mOpaque; - private final RectF mSrcRect = new RectF(); - private final RectF mDestRect = new RectF(); - - private boolean mImageBusy = false; - private final AtomicBoolean mRunning = new AtomicBoolean(); - private final AtomicBoolean mRequestAnimation = new AtomicBoolean(); - private final AtomicBoolean mFrameDirty = new AtomicBoolean(); - private final AtomicBoolean mNeedRelease = new AtomicBoolean(); - private final AtomicBoolean mReleased = new AtomicBoolean(); - - private Runnable mAnimateRunnable = null; - - private WeakReference mCallback; static { sThreadExecutor = new InfiniteThreadExecutor(10 * 1000, new LinkedList(), @@ -99,275 +68,24 @@ public class ImageTexture implements Texture, Animatable { sPVLock = new PVLock(3); } - public static class Uploader implements GLRoot.OnGLIdleListener { - private final ArrayDeque mTextures = - new ArrayDeque<>(INIT_CAPACITY); + private final ImageWrapper mImage; + private final Tile[] mTiles; // Can be modified in different threads. + private final int mWidth; + // Should be protected by "synchronized." + private final int mHeight; + private final boolean mOpaque; + private final RectF mSrcRect = new RectF(); + private final RectF mDestRect = new RectF(); + private final AtomicBoolean mRunning = new AtomicBoolean(); + private final AtomicBoolean mRequestAnimation = new AtomicBoolean(); + private final AtomicBoolean mFrameDirty = new AtomicBoolean(); + private final AtomicBoolean mNeedRelease = new AtomicBoolean(); + private final AtomicBoolean mReleased = new AtomicBoolean(); + private int mUploadIndex = 0; + private boolean mImageBusy = false; + private Runnable mAnimateRunnable = null; - private final GLRoot mGlRoot; - private boolean mIsQueued = false; - - public Uploader(GLRoot glRoot) { - mGlRoot = glRoot; - } - - public synchronized void clear() { - mTextures.clear(); - } - - public synchronized void addTexture(ImageTexture t) { - if (t.isReady()) return; - mTextures.addLast(t); - - if (mIsQueued) return; - mIsQueued = true; - mGlRoot.addOnGLIdleListener(this); - } - - @Override - public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { - ArrayDeque deque = mTextures; - synchronized (this) { - long now = SystemClock.uptimeMillis(); - long dueTime = now + UPLOAD_TILE_LIMIT; - while (now < dueTime && !deque.isEmpty()) { - ImageTexture t = deque.peekFirst(); - if (t.uploadNextTile(canvas)) { - deque.removeFirst(); - mGlRoot.requestRender(); - } - now = SystemClock.uptimeMillis(); - } - mIsQueued = !mTextures.isEmpty(); - - // return true to keep this listener in the queue - return mIsQueued; - } - } - } - - private static class Tile extends NativeTexture { - - @TileType - private int mTileType; - public int offsetX; - public int offsetY; - public ImageWrapper image; - public Tile nextFreeTile; - public int contentWidth; - public int contentHeight; - public int borderSize; - - public void setSize(@TileType int tileType, int width, int height) { - mTileType = tileType; - int tileSize; - if (tileType == TILE_SMALL) { - borderSize = SMALL_BORDER_SIZE; - tileSize = SMALL_TILE_SIZE; - } else if (tileType == TILE_LARGE) { - borderSize = LARGE_BORDER_SIZE; - tileSize = LARGE_TILE_SIZE; - } else { - throw new IllegalStateException("Not support tile type: " + tileType); - } - contentWidth = width; - contentHeight = height; - - mWidth = width + 2 * borderSize; - mHeight = height + 2 * borderSize; - mTextureWidth = tileSize; - mTextureHeight = tileSize; - } - - @Override - protected void texImage(boolean init) { - if (image != null && !image.isRecycled()) { - int w, h; - if (init) { - w = mTextureWidth; - h = mTextureHeight; - } else { - w = mWidth; - h = mHeight; - } - image.texImage(init, offsetX - borderSize, offsetY - borderSize, w, h); - } - } - - private void invalidate() { - invalidateContent(); - image = null; - } - - public void free() { - switch (mTileType) { - case TILE_SMALL: - freeSmallTile(this); - break; - case TILE_LARGE: - freeLargeTile(this); - break; - default: - throw new IllegalStateException("Not support tile type: " + mTileType); - } - } - - private static void freeSmallTile(Tile tile) { - tile.invalidate(); - synchronized (sFreeTileLock) { - tile.nextFreeTile = sSmallFreeTileHead; - sSmallFreeTileHead = tile; - } - } - - private static void freeLargeTile(Tile tile) { - tile.invalidate(); - synchronized (sFreeTileLock) { - tile.nextFreeTile = sLargeFreeTileHead; - sLargeFreeTileHead = tile; - } - } - } - - private static Tile obtainSmallTile() { - synchronized (sFreeTileLock) { - Tile result = sSmallFreeTileHead; - if (result == null) { - return new Tile(); - } else { - sSmallFreeTileHead = result.nextFreeTile; - result.nextFreeTile = null; - } - return result; - } - } - - private static Tile obtainLargeTile() { - synchronized (sFreeTileLock) { - Tile result = sLargeFreeTileHead; - if (result == null) { - return new Tile(); - } else { - sLargeFreeTileHead = result.nextFreeTile; - result.nextFreeTile = null; - } - return result; - } - } - - private class AnimateRunnable implements Runnable { - - public void doRun() { - long lastTime = System.nanoTime(); - long lastDelay = -1L; - - synchronized (mImage) { - // Check released, image busy, Need release - if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get()) { - mAnimateRunnable = null; - return; - } - // Obtain image - mImageBusy = true; - } - - if (!mImage.isCompleted()) { - try { - sPVLock.p(); - } catch (InterruptedException e) { - // Ignore - } - if (!mNeedRelease.get()) { - mImage.complete(); - } - sPVLock.v(); - } - - int frameCount = mImage.getFrameCount(); - - synchronized (mImage) { - // Release image - mImageBusy = false; - // Check need release, frameCount <= 1 - if (mNeedRelease.get() || frameCount <= 1) { - mAnimateRunnable = null; - return; - } - } - - if (mRequestAnimation.get()) { - mRunning.lazySet(true); - } - - for (;;) { - // Obtain - synchronized (mImage) { - // Check released, image busy, Need release, not running - if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get() || !mRunning.get()) { - mAnimateRunnable = null; - return; - } - // Obtain image - mImageBusy = true; - } - - mImage.advance(); - long delay = mImage.getDelay(); - long time = System.nanoTime(); - if (-1L != lastDelay) { - delay -= (time - lastTime) / 1000000 - lastDelay; - } - lastTime = time; - lastDelay = delay; - mFrameDirty.lazySet(true); - invalidateSelf(); - - synchronized (mImage) { - // Release image - mImageBusy = false; - } - - if (delay > 0) { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - // Ignore - } - } - } - } - - @Override - public void run() { - doRun(); - - while (mNeedRelease.get()) { - // Obtain - synchronized (mImage) { - // Check released - if (mReleased.get() || mImage.isImageRecycled()) { - break; - } - // Check image busy - if (mImageBusy) { - // Image is busy, means it is recycling - break; - } - // Obtain image - mImageBusy = true; - } - - if (!mReleased.get()) { - mImage.release(); - mReleased.lazySet(true); - } - - synchronized (mImage) { - // Release image - mImageBusy = false; - } - } - } - } + private WeakReference mCallback; /** * Call {@link ImageWrapper#obtain()} first @@ -443,8 +161,52 @@ public class ImageTexture implements Texture, Animatable { } } - public final void setCallback(Callback cb) { - mCallback = new WeakReference<>(cb); + private static Tile obtainSmallTile() { + synchronized (sFreeTileLock) { + Tile result = sSmallFreeTileHead; + if (result == null) { + return new Tile(); + } else { + sSmallFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + } + return result; + } + } + + private static Tile obtainLargeTile() { + synchronized (sFreeTileLock) { + Tile result = sLargeFreeTileHead; + if (result == null) { + return new Tile(); + } else { + sLargeFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + } + return result; + } + } + + // We want to draw the "source" on the "target". + // This method is to find the "output" rectangle which is + // the corresponding area of the "src". + // (x,y) target + // (x0,y0) source +---------------+ + // +----------+ | | + // | src | | output | + // | +--+ | linear map | +----+ | + // | +--+ | ----------> | | | | + // | | by (scaleX, scaleY) | +----+ | + // +----------+ | | + // Texture +---------------+ + // Canvas + private static void mapRect(RectF output, + RectF src, float x0, float y0, float x, float y, float scaleX, + float scaleY) { + output.set(x + (src.left - x0) * scaleX, + y + (src.top - y0) * scaleY, + x + (src.right - x0) * scaleX, + y + (src.bottom - y0) * scaleY); } public Callback getCallback() { @@ -454,6 +216,10 @@ public class ImageTexture implements Texture, Animatable { return null; } + public final void setCallback(Callback cb) { + mCallback = new WeakReference<>(cb); + } + public void invalidateSelf() { final Callback callback = getCallback(); if (callback != null) { @@ -537,28 +303,6 @@ public class ImageTexture implements Texture, Animatable { return mHeight; } - // We want to draw the "source" on the "target". - // This method is to find the "output" rectangle which is - // the corresponding area of the "src". - // (x,y) target - // (x0,y0) source +---------------+ - // +----------+ | | - // | src | | output | - // | +--+ | linear map | +----+ | - // | +--+ | ----------> | | | | - // | | by (scaleX, scaleY) | +----+ | - // +----------+ | | - // Texture +---------------+ - // Canvas - private static void mapRect(RectF output, - RectF src, float x0, float y0, float x, float y, float scaleX, - float scaleY) { - output.set(x + (src.left - x0) * scaleX, - y + (src.top - y0) * scaleY, - x + (src.right - x0) * scaleX, - y + (src.bottom - y0) * scaleY); - } - @RenderThread private void syncFrame() { if (mFrameDirty.getAndSet(false)) { @@ -620,7 +364,7 @@ public class ImageTexture implements Texture, Animatable { // Draws a mixed color of this texture and a specified color onto the // a rectangle. The used color is: from * (1 - ratio) + to * ratio. public void drawMixed(GLCanvas canvas, int color, float ratio, - int x, int y, int width, int height) { + int x, int y, int width, int height) { RectF src = mSrcRect; RectF dest = mDestRect; float scaleX = (float) width / mWidth; @@ -637,7 +381,7 @@ public class ImageTexture implements Texture, Animatable { } public void drawMixed(GLCanvas canvas, int color, float ratio, - RectF source, RectF target) { + RectF source, RectF target) { RectF src = mSrcRect; RectF dest = mDestRect; float x0 = source.left; @@ -699,7 +443,256 @@ public class ImageTexture implements Texture, Animatable { } } + @IntDef({TILE_SMALL, TILE_LARGE}) + @Retention(RetentionPolicy.SOURCE) + private @interface TileType { + } + public interface Callback { void invalidateImageTexture(ImageTexture who); } + + public static class Uploader implements GLRoot.OnGLIdleListener { + private final ArrayDeque mTextures = + new ArrayDeque<>(INIT_CAPACITY); + + private final GLRoot mGlRoot; + private boolean mIsQueued = false; + + public Uploader(GLRoot glRoot) { + mGlRoot = glRoot; + } + + public synchronized void clear() { + mTextures.clear(); + } + + public synchronized void addTexture(ImageTexture t) { + if (t.isReady()) return; + mTextures.addLast(t); + + if (mIsQueued) return; + mIsQueued = true; + mGlRoot.addOnGLIdleListener(this); + } + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + ArrayDeque deque = mTextures; + synchronized (this) { + long now = SystemClock.uptimeMillis(); + long dueTime = now + UPLOAD_TILE_LIMIT; + while (now < dueTime && !deque.isEmpty()) { + ImageTexture t = deque.peekFirst(); + if (t.uploadNextTile(canvas)) { + deque.removeFirst(); + mGlRoot.requestRender(); + } + now = SystemClock.uptimeMillis(); + } + mIsQueued = !mTextures.isEmpty(); + + // return true to keep this listener in the queue + return mIsQueued; + } + } + } + + private static class Tile extends NativeTexture { + + public int offsetX; + public int offsetY; + public ImageWrapper image; + public Tile nextFreeTile; + public int contentWidth; + public int contentHeight; + public int borderSize; + @TileType + private int mTileType; + + private static void freeSmallTile(Tile tile) { + tile.invalidate(); + synchronized (sFreeTileLock) { + tile.nextFreeTile = sSmallFreeTileHead; + sSmallFreeTileHead = tile; + } + } + + private static void freeLargeTile(Tile tile) { + tile.invalidate(); + synchronized (sFreeTileLock) { + tile.nextFreeTile = sLargeFreeTileHead; + sLargeFreeTileHead = tile; + } + } + + public void setSize(@TileType int tileType, int width, int height) { + mTileType = tileType; + int tileSize; + if (tileType == TILE_SMALL) { + borderSize = SMALL_BORDER_SIZE; + tileSize = SMALL_TILE_SIZE; + } else if (tileType == TILE_LARGE) { + borderSize = LARGE_BORDER_SIZE; + tileSize = LARGE_TILE_SIZE; + } else { + throw new IllegalStateException("Not support tile type: " + tileType); + } + contentWidth = width; + contentHeight = height; + + mWidth = width + 2 * borderSize; + mHeight = height + 2 * borderSize; + mTextureWidth = tileSize; + mTextureHeight = tileSize; + } + + @Override + protected void texImage(boolean init) { + if (image != null && !image.isRecycled()) { + int w, h; + if (init) { + w = mTextureWidth; + h = mTextureHeight; + } else { + w = mWidth; + h = mHeight; + } + image.texImage(init, offsetX - borderSize, offsetY - borderSize, w, h); + } + } + + private void invalidate() { + invalidateContent(); + image = null; + } + + public void free() { + switch (mTileType) { + case TILE_SMALL: + freeSmallTile(this); + break; + case TILE_LARGE: + freeLargeTile(this); + break; + default: + throw new IllegalStateException("Not support tile type: " + mTileType); + } + } + } + + private class AnimateRunnable implements Runnable { + + public void doRun() { + long lastTime = System.nanoTime(); + long lastDelay = -1L; + + synchronized (mImage) { + // Check released, image busy, Need release + if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get()) { + mAnimateRunnable = null; + return; + } + // Obtain image + mImageBusy = true; + } + + if (!mImage.isCompleted()) { + try { + sPVLock.p(); + } catch (InterruptedException e) { + // Ignore + } + if (!mNeedRelease.get()) { + mImage.complete(); + } + sPVLock.v(); + } + + int frameCount = mImage.getFrameCount(); + + synchronized (mImage) { + // Release image + mImageBusy = false; + // Check need release, frameCount <= 1 + if (mNeedRelease.get() || frameCount <= 1) { + mAnimateRunnable = null; + return; + } + } + + if (mRequestAnimation.get()) { + mRunning.lazySet(true); + } + + for (; ; ) { + // Obtain + synchronized (mImage) { + // Check released, image busy, Need release, not running + if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get() || !mRunning.get()) { + mAnimateRunnable = null; + return; + } + // Obtain image + mImageBusy = true; + } + + mImage.advance(); + long delay = mImage.getDelay(); + long time = System.nanoTime(); + if (-1L != lastDelay) { + delay -= (time - lastTime) / 1000000 - lastDelay; + } + lastTime = time; + lastDelay = delay; + mFrameDirty.lazySet(true); + invalidateSelf(); + + synchronized (mImage) { + // Release image + mImageBusy = false; + } + + if (delay > 0) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + + @Override + public void run() { + doRun(); + + while (mNeedRelease.get()) { + // Obtain + synchronized (mImage) { + // Check released + if (mReleased.get() || mImage.isImageRecycled()) { + break; + } + // Check image busy + if (mImageBusy) { + // Image is busy, means it is recycling + break; + } + // Obtain image + mImageBusy = true; + } + + if (!mReleased.get()) { + mImage.release(); + mReleased.lazySet(true); + } + + synchronized (mImage) { + // Release image + mImageBusy = false; + } + } + } + } } diff --git a/app/src/main/java/com/hippo/glview/image/ImageWrapper.java b/app/src/main/java/com/hippo/glview/image/ImageWrapper.java index d2f5ceb84..99093406b 100644 --- a/app/src/main/java/com/hippo/glview/image/ImageWrapper.java +++ b/app/src/main/java/com/hippo/glview/image/ImageWrapper.java @@ -18,7 +18,6 @@ package com.hippo.glview.image; import android.graphics.Bitmap; import android.graphics.Rect; - import android.util.Log; import androidx.annotation.NonNull; @@ -71,7 +70,7 @@ public class ImageWrapper { */ public void setCutPercent(float left, float top, float right, float bottom) { setCutRect((int) (getWidth() * left), (int) (getHeight() * top), - (int) (getWidth() * right), (int) (getHeight() * bottom)); + (int) (getWidth() * right), (int) (getHeight() * bottom)); } /** @@ -143,7 +142,7 @@ public class ImageWrapper { * @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int) */ public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY, - int width, int height, boolean fillBlank, int defaultColor) { + int width, int height, boolean fillBlank, int defaultColor) { mImage.render(srcX + mCut.left, srcY + mCut.top, dst, dstX, dstY, width, height, fillBlank, defaultColor); } diff --git a/app/src/main/java/com/hippo/glview/util/ApiHelper.java b/app/src/main/java/com/hippo/glview/util/ApiHelper.java index ce8cb76f9..5f8737e6e 100644 --- a/app/src/main/java/com/hippo/glview/util/ApiHelper.java +++ b/app/src/main/java/com/hippo/glview/util/ApiHelper.java @@ -27,71 +27,99 @@ import android.view.WindowManager; import java.lang.reflect.Field; public class ApiHelper { - public interface VERSION_CODES { - // These value are copied from Build.VERSION_CODES - int GINGERBREAD_MR1 = 10; - int HONEYCOMB = 11; - int HONEYCOMB_MR1 = 12; - int HONEYCOMB_MR2 = 13; - int ICE_CREAM_SANDWICH = 14; - int ICE_CREAM_SANDWICH_MR1 = 15; - int JELLY_BEAN = 16; - int JELLY_BEAN_MR1 = 17; - int JELLY_BEAN_MR2 = 18; - } - public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16; - public static final boolean USE_888_PIXEL_FORMAT = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - public static final boolean ENABLE_PHOTO_EDITOR = Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; - public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE = hasField(View.class, "SYSTEM_UI_FLAG_LAYOUT_STABLE"); - public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION = hasField(View.class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION"); - public static final boolean HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT = hasField(MediaColumns.class, "WIDTH"); - public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_FACTORY = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - public static final boolean HAS_SET_BEAM_PUSH_URIS = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - public static final boolean HAS_SET_DEFALT_BUFFER_SIZE = hasMethod( "android.graphics.SurfaceTexture", "setDefaultBufferSize", int.class, int.class); - public static final boolean HAS_RELEASE_SURFACE_TEXTURE = hasMethod( "android.graphics.SurfaceTexture", "release"); - public static final boolean HAS_SURFACE_TEXTURE = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - public static final boolean HAS_MTP = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1; - public static final boolean HAS_AUTO_FOCUS_MOVE_CALLBACK = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - public static final boolean HAS_REMOTE_VIEWS_SERVICE = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - public static final boolean HAS_INTENT_EXTRA_LOCAL_ONLY = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - public static final boolean HAS_SET_SYSTEM_UI_VISIBILITY = hasMethod(View.class, "setSystemUiVisibility", int.class); - public static final boolean HAS_FACE_DETECTION; + public static final boolean HAS_GET_CAMERA_DISABLED = + hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class); + public static final boolean HAS_MEDIA_ACTION_SOUND = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_TIME_LAPSE_RECORDING = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_ZOOM_WHEN_RECORDING = + Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; + public static final boolean HAS_CAMERA_FOCUS_AREA = + Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; + public static final boolean HAS_CAMERA_METERING_AREA = + Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; + public static final boolean HAS_MOTION_EVENT_TRANSFORM = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_EFFECTS_RECORDING = false; + // "Background" filter does not have "context" input port in jelly bean. + public static final boolean HAS_EFFECTS_RECORDING_CONTEXT_INPUT = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; + public static final boolean HAS_GET_SUPPORTED_VIDEO_SIZE = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_SET_ICON_ATTRIBUTE = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_SURFACE_TEXTURE_RECORDING = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_ACTION_BAR = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + // Ex: View.setTranslationX. + public static final boolean HAS_VIEW_TRANSFORM_PROPERTIES = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_CAMERA_HDR = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; + public static final boolean HAS_OPTIONS_IN_MUTABLE = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK = + Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; + public static final boolean HAS_VIEW_PROPERTY_ANIMATOR = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1; + public static final boolean HAS_POST_ON_ANIMATION = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_OBJECT_ANIMATION = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_GLES20_REQUIRED = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_ROTATION_ANIMATION = + hasField(WindowManager.LayoutParams.class, "rotationAnimation"); + public static final boolean HAS_ORIENTATION_LOCK = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2; + public static final boolean HAS_CANCELLATION_SIGNAL = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_MEDIA_MUXER = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2; + public static final boolean HAS_DISPLAY_LISTENER = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; + static { boolean hasFaceDetection = false; try { @@ -99,101 +127,16 @@ public class ApiHelper { "android.hardware.Camera$FaceDetectionListener"); hasFaceDetection = hasMethod(Camera.class, "setFaceDetectionListener", listenerClass) && - hasMethod(Camera.class, "startFaceDetection") && - hasMethod(Camera.class, "stopFaceDetection") && - hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces"); + hasMethod(Camera.class, "startFaceDetection") && + hasMethod(Camera.class, "stopFaceDetection") && + hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces"); } catch (Throwable t) { } HAS_FACE_DETECTION = hasFaceDetection; } - public static final boolean HAS_GET_CAMERA_DISABLED = - hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class); - - public static final boolean HAS_MEDIA_ACTION_SOUND = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_TIME_LAPSE_RECORDING = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_ZOOM_WHEN_RECORDING = - Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; - - public static final boolean HAS_CAMERA_FOCUS_AREA = - Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; - - public static final boolean HAS_CAMERA_METERING_AREA = - Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; - - public static final boolean HAS_MOTION_EVENT_TRANSFORM = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_EFFECTS_RECORDING = false; - - // "Background" filter does not have "context" input port in jelly bean. - public static final boolean HAS_EFFECTS_RECORDING_CONTEXT_INPUT = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; - - public static final boolean HAS_GET_SUPPORTED_VIDEO_SIZE = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_SET_ICON_ATTRIBUTE = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_SURFACE_TEXTURE_RECORDING = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_ACTION_BAR = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - // Ex: View.setTranslationX. - public static final boolean HAS_VIEW_TRANSFORM_PROPERTIES = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_CAMERA_HDR = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; - - public static final boolean HAS_OPTIONS_IN_MUTABLE = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK = - Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; - - public static final boolean HAS_VIEW_PROPERTY_ANIMATOR = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1; - - public static final boolean HAS_POST_ON_ANIMATION = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_OBJECT_ANIMATION = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_GLES20_REQUIRED = - Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; - - public static final boolean HAS_ROTATION_ANIMATION = - hasField(WindowManager.LayoutParams.class, "rotationAnimation"); - - public static final boolean HAS_ORIENTATION_LOCK = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2; - - public static final boolean HAS_CANCELLATION_SIGNAL = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_MEDIA_MUXER = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2; - - public static final boolean HAS_DISPLAY_LISTENER = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; - public static int getIntFieldIfExists(Class klass, String fieldName, - Class obj, int defaultVal) { + Class obj, int defaultVal) { try { Field f = klass.getDeclaredField(fieldName); return f.getInt(obj); @@ -212,7 +155,7 @@ public class ApiHelper { } private static boolean hasMethod(String className, String methodName, - Class... parameterTypes) { + Class... parameterTypes) { try { Class klass = Class.forName(className); klass.getDeclaredMethod(methodName, parameterTypes); @@ -223,7 +166,7 @@ public class ApiHelper { } private static boolean hasMethod( - Class klass, String methodName, Class ... paramTypes) { + Class klass, String methodName, Class... paramTypes) { try { klass.getDeclaredMethod(methodName, paramTypes); return true; @@ -231,4 +174,17 @@ public class ApiHelper { return false; } } + + public interface VERSION_CODES { + // These value are copied from Build.VERSION_CODES + int GINGERBREAD_MR1 = 10; + int HONEYCOMB = 11; + int HONEYCOMB_MR1 = 12; + int HONEYCOMB_MR2 = 13; + int ICE_CREAM_SANDWICH = 14; + int ICE_CREAM_SANDWICH_MR1 = 15; + int JELLY_BEAN = 16; + int JELLY_BEAN_MR1 = 17; + int JELLY_BEAN_MR2 = 18; + } } diff --git a/app/src/main/java/com/hippo/glview/util/GalleryUtils.java b/app/src/main/java/com/hippo/glview/util/GalleryUtils.java index 48075cbc6..654fcf466 100644 --- a/app/src/main/java/com/hippo/glview/util/GalleryUtils.java +++ b/app/src/main/java/com/hippo/glview/util/GalleryUtils.java @@ -17,31 +17,32 @@ package com.hippo.glview.util; import android.graphics.Color; + import com.hippo.yorozuya.AssertError; + import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class GalleryUtils { private static final String TAG = "GalleryUtils"; + private static final List sRenderThreads = new CopyOnWriteArrayList<>(); public static float[] intColorToFloatARGBArray(int from) { - return new float[] { - Color.alpha(from) / 255f, - Color.red(from) / 255f, - Color.green(from) / 255f, - Color.blue(from) / 255f + return new float[]{ + Color.alpha(from) / 255f, + Color.red(from) / 255f, + Color.green(from) / 255f, + Color.blue(from) / 255f }; } - public static int floatARGBArrayTointColor(float[] from) { - return Color.argb((int) (from[0] * 255), (int) (from[1] * 255), (int) (from[2] * 255), (int) (from[3] * 255)); - } - // Below are used the detect using database in the render thread. It only // works most of the time, but that's ok because it's for debugging only. - private static final List sRenderThreads = new CopyOnWriteArrayList<>(); + public static int floatARGBArrayTointColor(float[] from) { + return Color.argb((int) (from[0] * 255), (int) (from[1] * 255), (int) (from[2] * 255), (int) (from[3] * 255)); + } public static void setRenderThread() { sRenderThreads.add(Thread.currentThread().hashCode()); diff --git a/app/src/main/java/com/hippo/glview/util/MotionEventHelper.java b/app/src/main/java/com/hippo/glview/util/MotionEventHelper.java index 980020d3f..297f47bfe 100644 --- a/app/src/main/java/com/hippo/glview/util/MotionEventHelper.java +++ b/app/src/main/java/com/hippo/glview/util/MotionEventHelper.java @@ -21,7 +21,8 @@ import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; public final class MotionEventHelper { - private MotionEventHelper() {} + private MotionEventHelper() { + } public static MotionEvent transformEvent(MotionEvent e, Matrix m) { // We try to use the new transform method if possible because it uses @@ -58,16 +59,16 @@ public final class MotionEventHelper { // Copy the x and y coordinates into an array, map them, and copy back. float[] xy = new float[pointerCoords.length * 2]; - for (int i = 0; i < pointerCount;i++) { + for (int i = 0; i < pointerCount; i++) { xy[2 * i] = pointerCoords[i].x; xy[2 * i + 1] = pointerCoords[i].y; } m.mapPoints(xy); - for (int i = 0; i < pointerCount;i++) { + for (int i = 0; i < pointerCount; i++) { pointerCoords[i].x = xy[2 * i]; pointerCoords[i].y = xy[2 * i + 1]; pointerCoords[i].orientation = transformAngle( - m, pointerCoords[i].orientation); + m, pointerCoords[i].orientation); } MotionEvent n = MotionEvent.obtain(downTime, eventTime, action, diff --git a/app/src/main/java/com/hippo/glview/view/GLRoot.java b/app/src/main/java/com/hippo/glview/view/GLRoot.java index e582f5742..98766ef47 100644 --- a/app/src/main/java/com/hippo/glview/view/GLRoot.java +++ b/app/src/main/java/com/hippo/glview/view/GLRoot.java @@ -24,32 +24,45 @@ import com.hippo.glview.glrenderer.GLCanvas; public interface GLRoot { - // Listener will be called when GL is idle AND before each frame. - // Mainly used for uploading textures. - interface OnGLIdleListener { - boolean onGLIdle(GLCanvas canvas, boolean renderRequested); - } - void addOnGLIdleListener(OnGLIdleListener listener); + void registerLaunchedAnimation(CanvasAnimation animation); + void requestRenderForced(); + void requestRender(); + void requestLayoutContentPane(); void lockRenderThread(); + void unlockRenderThread(); void setContentPane(GLView content); + void setOrientationSource(OrientationSource source); + int getDisplayRotation(); + int getCompensation(); + Matrix getCompensationMatrix(); + void freeze(); + void unfreeze(); + void setLightsOutMode(boolean enabled); Context getContext(); int getWidth(); + int getHeight(); + + // Listener will be called when GL is idle AND before each frame. + // Mainly used for uploading textures. + interface OnGLIdleListener { + boolean onGLIdle(GLCanvas canvas, boolean renderRequested); + } } diff --git a/app/src/main/java/com/hippo/glview/view/GLRootView.java b/app/src/main/java/com/hippo/glview/view/GLRootView.java index f2a116f46..66b2b8f90 100644 --- a/app/src/main/java/com/hippo/glview/view/GLRootView.java +++ b/app/src/main/java/com/hippo/glview/view/GLRootView.java @@ -47,10 +47,12 @@ import com.hippo.tuxiang.EGLContextFactory; import com.hippo.tuxiang.GLSurfaceView; import com.hippo.tuxiang.Renderer; import com.hippo.yorozuya.AssertUtils; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; @@ -72,146 +74,44 @@ public class GLRootView extends GLSurfaceView private static final String TAG = "GLRootView"; private static final boolean DEBUG_FPS = false; - private int mFrameCount = 0; - private long mFrameCountingStart = 0; - private static final boolean DEBUG_INVALIDATE = false; - private int mInvalidateColor = 0; - private static final boolean DEBUG_DRAWING_STAT = false; - private static final int FLAG_INITIALIZED = 1; private static final int FLAG_NEED_LAYOUT = 2; - + // mCompensationMatrix maps the coordinates of touch events. It is kept sync + // with mCompensation. + private final Matrix mCompensationMatrix = new Matrix(); + private final ArrayList mAnimations = + new ArrayList<>(); + private final ArrayDeque mIdleListeners = + new ArrayDeque<>(); + private final IdleRunner mIdleRunner = new IdleRunner(); + private final ReentrantLock mRenderLock = new ReentrantLock(); + private final Condition mFreezeCondition = + mRenderLock.newCondition(); + private final Runnable mRequestRenderOnAnimationFrame = new Runnable() { + @Override + public void run() { + superRequestRender(); + } + }; + private int mFrameCount = 0; + private long mFrameCountingStart = 0; + private int mInvalidateColor = 0; private GL11 mGL; private GLCanvas mCanvas; private GLView mContentView; - private OrientationSource mOrientationSource; // mCompensation is the difference between the UI orientation on GLCanvas // and the framework orientation. See OrientationManager for details. private int mCompensation; - // mCompensationMatrix maps the coordinates of touch events. It is kept sync - // with mCompensation. - private final Matrix mCompensationMatrix = new Matrix(); private int mDisplayRotation; - private int mFlags = FLAG_NEED_LAYOUT; private volatile boolean mRenderRequested = false; - - private final ArrayList mAnimations = - new ArrayList<>(); - - private final ArrayDeque mIdleListeners = - new ArrayDeque<>(); - - private final IdleRunner mIdleRunner = new IdleRunner(); - - private final ReentrantLock mRenderLock = new ReentrantLock(); - private final Condition mFreezeCondition = - mRenderLock.newCondition(); private boolean mFreeze; - private boolean mInDownState = false; - private int mEGLContextClientVersion; - private class GLRootRenderer implements Renderer { - - /** - * Called when the context is created, possibly after automatic destruction. - */ - @Override - public void onSurfaceCreated(GL10 gl1, EGLConfig config) { - GL11 gl = (GL11) gl1; - if (mGL != null) { - // The GL Object has changed - Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); - } - mRenderLock.lock(); - try { - mGL = gl; - mCanvas = mEGLContextClientVersion == 2 ? new GLES20Canvas() : new GLES11Canvas(gl); - BasicTexture.invalidateAllTextures(); - } finally { - mRenderLock.unlock(); - } - - if (DEBUG_FPS) { - setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); - } else { - setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); - } - } - - /** - * Called when the OpenGL surface is recreated without destroying the - * context. - */ - @Override - public void onSurfaceChanged(GL10 gl1, int width, int height) { - Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString()); - Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); - GalleryUtils.setRenderThread(); - GL11 gl = (GL11) gl1; - AssertUtils.assertTrue(mGL == gl); - - mCanvas.setSize(width, height); - } - - @Override - public boolean onDrawFrame(GL10 gl) { - AnimationTime.update(); - - long t0 = System.nanoTime(); - - mRenderLock.lock(); - - while (mFreeze) { - mFreezeCondition.awaitUninterruptibly(); - } - - try { - onDrawFrameLocked(gl); - } finally { - mRenderLock.unlock(); - } - - long t = System.nanoTime(); - long duration = (t - t0) / 1000000; - - if (duration > 8) { - Log.v(TAG, "--- " + duration + " ---"); - } - - return true; - } - - @Override - public void onGLThreadStart() { } - - @Override - public void onGLThreadExit() { - if (mContentView != null && mContentView.isAttachedToRoot()) { - mContentView.detachFromRoot(); - } - } - - @Override - public void onGLThreadPause() { - if (mContentView != null) { - mContentView.pause(); - } - } - - @Override - public void onGLThreadResume() { - if (mContentView != null) { - mContentView.resume(); - } - } - } - public GLRootView(Context context) { this(context, null); } @@ -294,13 +194,6 @@ public class GLRootView extends GLSurfaceView } } - private final Runnable mRequestRenderOnAnimationFrame = new Runnable() { - @Override - public void run() { - superRequestRender(); - } - }; - private void superRequestRender() { super.requestRender(); } @@ -407,7 +300,7 @@ public class GLRootView extends GLSurfaceView mCanvas.save(GLCanvas.SAVE_FLAG_ALL); rotateCanvas(-mCompensation); if (mContentView != null) { - mContentView.render(mCanvas); + mContentView.render(mCanvas); } else { // Make sure we always draw something to prevent displaying garbage mCanvas.clearBuffer(); @@ -485,39 +378,6 @@ public class GLRootView extends GLSurfaceView } } - private class IdleRunner implements Runnable { - // true if the idle runner is in the queue - private boolean mActive = false; - - @Override - public void run() { - OnGLIdleListener listener; - synchronized (mIdleListeners) { - mActive = false; - if (mIdleListeners.isEmpty()) return; - listener = mIdleListeners.removeFirst(); - } - mRenderLock.lock(); - boolean keepInQueue; - try { - keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested); - } finally { - mRenderLock.unlock(); - } - synchronized (mIdleListeners) { - if (keepInQueue) mIdleListeners.addLast(listener); - if (!mRenderRequested && !mIdleListeners.isEmpty()) enable(); - } - } - - public void enable() { - // Who gets the flag can add it to the queue - if (mActive) return; - mActive = true; - queueEvent(this); - } - } - @Override public void lockRenderThread() { mRenderLock.lock(); @@ -643,6 +503,136 @@ public class GLRootView extends GLSurfaceView } } + private class GLRootRenderer implements Renderer { + + /** + * Called when the context is created, possibly after automatic destruction. + */ + @Override + public void onSurfaceCreated(GL10 gl1, EGLConfig config) { + GL11 gl = (GL11) gl1; + if (mGL != null) { + // The GL Object has changed + Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); + } + mRenderLock.lock(); + try { + mGL = gl; + mCanvas = mEGLContextClientVersion == 2 ? new GLES20Canvas() : new GLES11Canvas(gl); + BasicTexture.invalidateAllTextures(); + } finally { + mRenderLock.unlock(); + } + + if (DEBUG_FPS) { + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } else { + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + } + } + + /** + * Called when the OpenGL surface is recreated without destroying the + * context. + */ + @Override + public void onSurfaceChanged(GL10 gl1, int width, int height) { + Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString()); + Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); + GalleryUtils.setRenderThread(); + GL11 gl = (GL11) gl1; + AssertUtils.assertTrue(mGL == gl); + + mCanvas.setSize(width, height); + } + + @Override + public boolean onDrawFrame(GL10 gl) { + AnimationTime.update(); + + long t0 = System.nanoTime(); + + mRenderLock.lock(); + + while (mFreeze) { + mFreezeCondition.awaitUninterruptibly(); + } + + try { + onDrawFrameLocked(gl); + } finally { + mRenderLock.unlock(); + } + + long t = System.nanoTime(); + long duration = (t - t0) / 1000000; + + if (duration > 8) { + Log.v(TAG, "--- " + duration + " ---"); + } + + return true; + } + + @Override + public void onGLThreadStart() { + } + + @Override + public void onGLThreadExit() { + if (mContentView != null && mContentView.isAttachedToRoot()) { + mContentView.detachFromRoot(); + } + } + + @Override + public void onGLThreadPause() { + if (mContentView != null) { + mContentView.pause(); + } + } + + @Override + public void onGLThreadResume() { + if (mContentView != null) { + mContentView.resume(); + } + } + } + + private class IdleRunner implements Runnable { + // true if the idle runner is in the queue + private boolean mActive = false; + + @Override + public void run() { + OnGLIdleListener listener; + synchronized (mIdleListeners) { + mActive = false; + if (mIdleListeners.isEmpty()) return; + listener = mIdleListeners.removeFirst(); + } + mRenderLock.lock(); + boolean keepInQueue; + try { + keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested); + } finally { + mRenderLock.unlock(); + } + synchronized (mIdleListeners) { + if (keepInQueue) mIdleListeners.addLast(listener); + if (!mRenderRequested && !mIdleListeners.isEmpty()) enable(); + } + } + + public void enable() { + // Who gets the flag can add it to the queue + if (mActive) return; + mActive = true; + queueEvent(this); + } + } + // Always chose a config private class ConfigChooser implements EGLConfigChooser { @@ -651,7 +641,7 @@ public class GLRootView extends GLSurfaceView @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] num_config = new int[1]; - int[] configSpec = new int[] { EGL10.EGL_NONE }; + int[] configSpec = new int[]{EGL10.EGL_NONE}; if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) { throw new IllegalArgumentException("eglChooseConfig failed"); } @@ -660,7 +650,7 @@ public class GLRootView extends GLSurfaceView if (numConfigs <= 0) { throw new IllegalArgumentException( - "No configs match configSpec"); + "No configs match configSpec"); } EGLConfig[] configs = new EGLConfig[numConfigs]; @@ -690,24 +680,24 @@ public class GLRootView extends GLSurfaceView for (int i = 0, n = configs.length; i < n; i++) { final EGLConfig config = configs[i]; final int redSize = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); + EGL10.EGL_RED_SIZE, 0); final int greenSize = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); + EGL10.EGL_GREEN_SIZE, 0); final int blueSize = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); + EGL10.EGL_BLUE_SIZE, 0); final int alphaSize = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); + EGL10.EGL_ALPHA_SIZE, 0); final int sampleBuffers = findConfigAttrib(egl, display, config, - EGL10.EGL_SAMPLE_BUFFERS, 0); + EGL10.EGL_SAMPLE_BUFFERS, 0); final int samples = findConfigAttrib(egl, display, config, - EGL10.EGL_SAMPLES, 0); + EGL10.EGL_SAMPLES, 0); final int depth = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); + EGL10.EGL_DEPTH_SIZE, 0); final int stencil = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); + EGL10.EGL_STENCIL_SIZE, 0); final int score = redSize + greenSize + blueSize + alphaSize + - sampleBuffers + samples - depth - stencil; + sampleBuffers + samples - depth - stencil; if (score > maxScore) { maxScore = score; @@ -719,7 +709,7 @@ public class GLRootView extends GLSurfaceView } private int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { + EGLConfig config, int attribute, int defaultValue) { if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { return mValue[0]; } @@ -734,7 +724,7 @@ public class GLRootView extends GLSurfaceView @Override public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, - EGL10.EGL_NONE }; + EGL10.EGL_NONE}; return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, mEGLContextClientVersion != 0 ? attrib_list : null); diff --git a/app/src/main/java/com/hippo/glview/view/GLView.java b/app/src/main/java/com/hippo/glview/view/GLView.java index 2c1aec304..856471579 100644 --- a/app/src/main/java/com/hippo/glview/view/GLView.java +++ b/app/src/main/java/com/hippo/glview/view/GLView.java @@ -51,37 +51,20 @@ import java.util.ArrayList; // lockRendering() if the rendering thread should not run at the same time. // public class GLView implements TouchOwner { - private static final String TAG = "GLView"; - - private static final boolean DEBUG_DRAW_BOUNDS = false; - - @IntDef({VISIBLE, INVISIBLE, GONE}) - @Retention(RetentionPolicy.SOURCE) - public @interface Visibility {} - - public static final int VISIBLE = 0b00000000; - public static final int INVISIBLE = 0b00000001; - public static final int GONE = 0b00000010; - public static final int VISIBILITY_INVALID = 0b00000011; - - private static final int FLAG_INVISIBLE = 0b00000011; - private static final int FLAG_SET_MEASURED_SIZE = 0b00000100; - private static final int FLAG_LAYOUT_REQUESTED = 0b00001000; - + public static final int VISIBLE = 0b00000000; + public static final int INVISIBLE = 0b00000001; + public static final int GONE = 0b00000010; + public static final int VISIBILITY_INVALID = 0b00000011; /** * Used to mark a GlView that has no ID. */ public static final int NO_ID = -1; - - public interface OnClickListener { - boolean onClick(GLView v); - } - - public interface OnLongClickListener { - boolean onLongClick(GLView v); - } - protected final static GLPaint mDrawBoundsPaint; + private static final String TAG = "GLView"; + private static final boolean DEBUG_DRAW_BOUNDS = false; + private static final int FLAG_INVISIBLE = 0b00000011; + private static final int FLAG_SET_MEASURED_SIZE = 0b00000100; + private static final int FLAG_LAYOUT_REQUESTED = 0b00001000; static { mDrawBoundsPaint = new GLPaint(); @@ -93,33 +76,26 @@ public class GLView implements TouchOwner { * Position in parent, just like left, top, right, bottom in {@link android.view.View} */ protected final Rect mBounds = new Rect(); + protected final Rect mPaddings = new Rect(); /** * Position in root */ private final int[] mPositionInRoot = new int[2]; - protected final Rect mPaddings = new Rect(); private final Rect mTempRect = new Rect(); - - private GLRoot mRoot; protected GLView mParent; - private ArrayList mComponents; - private GLView mMotionTarget; - - private CanvasAnimation mAnimation; - - private int mViewFlags = 0; - protected int mMeasuredWidth = 0; protected int mMeasuredHeight = 0; - - private int mLastWidthSpec = -1; - private int mLastHeightSpec = -1; - protected int mScrollY = 0; protected int mScrollX = 0; protected int mScrollHeight = 0; protected int mScrollWidth = 0; - + private GLRoot mRoot; + private ArrayList mComponents; + private GLView mMotionTarget; + private CanvasAnimation mAnimation; + private int mViewFlags = 0; + private int mLastWidthSpec = -1; + private int mLastHeightSpec = -1; /** * The GlView's identifier. * @@ -127,33 +103,192 @@ public class GLView implements TouchOwner { * @see #getId() */ private int mID = NO_ID; - /** * The minimum height of the view. We'll try our best to have the height * of this view to at least this amount. */ private int mMinHeight; - /** * The minimum width of the view. We'll try our best to have the width * of this view to at least this amount. */ private int mMinWidth; - private LayoutParams mLayoutParams; - private int mBackgroundColor = Color.TRANSPARENT; - private TouchHelper mTouchHelper; - private float mHotspotX; private float mHotspotY; - private boolean mPressed; - private OnClickListener mOnClickListener; private OnLongClickListener mOnLongClickListener; + /** + * Utility to return a default begin. Uses the supplied number as left or top. + * + * @param size the parent size + * @param specSize the child size + * @param paddingBeign the padding begin + * @param paddingFinish the padding finish + * @param position the {@link Gravity#POSITION_BEGIN} or {@link Gravity#POSITION_CENTER} or + * {@link Gravity#POSITION_FINISH} + * @return the begin size + */ + public static int getDefaultBegin(int size, int specSize, int paddingBeign, int paddingFinish, + @Gravity.PositionMode int position) { + if (position == Gravity.POSITION_FINISH) { + return size - paddingFinish - specSize; + } else if (position == Gravity.POSITION_CENTER) { + return ((size - paddingBeign - paddingFinish) / 2) - (specSize / 2) + paddingBeign; + } else { + return paddingBeign; + } + } + + /** + * Utility to return a default size. Uses the supplied size if the + * MeasureSpec imposed no constraints. Will get larger if allowed + * by the MeasureSpec. + *

+ * It works differently from {@link android.view.View#getDefaultSize(int, int)}. + * For {@link MeasureSpec#AT_MOST}, size is suggestion size + * if it is small then spec size or it is not zero, + * otherwise spec size. + * + * @param size Default size for this view + * @param measureSpec Constraints imposed by the parent + * @return The size this view should be. + */ + public static int getDefaultSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + case MeasureSpec.AT_MOST: + return size == 0 ? specSize : Math.min(size, specSize); + } + return result; + } + + /** + * Utility to return a max size. Uses the supplied size if the + * MeasureSpec imposed no constraints. Will get larger if allowed + * by the MeasureSpec. + *

+ * It works the same as {@link android.view.View#getDefaultSize(int, int)}. + * + * @param size Default size for this view + * @param measureSpec Constraints imposed by the parent + * @return The size this view should be. + */ + public static int getMaxSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.EXACTLY: + case MeasureSpec.AT_MOST: + result = specSize; + break; + } + return result; + } + + public static int getComponentSpec(int spec, int childSize) { + int specMode = MeasureSpec.getMode(spec); + int specSize = MeasureSpec.getSize(spec); + + int size = Math.max(0, specSize); + + int resultSize = 0; + int resultMode = 0; + + switch (specMode) { + // Parent has imposed an exact size on us + case MeasureSpec.EXACTLY: + if (childSize >= 0) { + resultSize = childSize; + resultMode = MeasureSpec.EXACTLY; + } else if (childSize == LayoutParams.MATCH_PARENT) { + // Child wants to be our size. So be it. + resultSize = size; + resultMode = MeasureSpec.EXACTLY; + } else if (childSize == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } + break; + + // Parent has imposed a maximum size on us + case MeasureSpec.AT_MOST: + if (childSize >= 0) { + // Child wants a specific size... so be it + resultSize = childSize; + resultMode = MeasureSpec.EXACTLY; + } else if (childSize == LayoutParams.MATCH_PARENT) { + // Child wants to be our size, but our size is not fixed. + // Constrain child to not be bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } else if (childSize == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } + break; + + // Parent asked to see how big we want to be + case MeasureSpec.UNSPECIFIED: + if (childSize >= 0) { + // Child wants a specific size... let him have it + resultSize = childSize; + resultMode = MeasureSpec.EXACTLY; + } else if (childSize == LayoutParams.MATCH_PARENT) { + // Child wants to be our size... find out how big it should + // be + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + } else if (childSize == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size.... find out how + // big it should be + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + } + break; + } + return MeasureSpec.makeMeasureSpec(resultSize, resultMode); + } + + public static void measureComponent(GLView component, int widthSpec, int heightSpec) { + final LayoutParams lp = component.getLayoutParams(); + final int componentWidthSpec = getComponentSpec(widthSpec, lp.width); + final int componentHeightSpec = getComponentSpec(heightSpec, lp.height); + component.measure(componentWidthSpec, componentHeightSpec); + } + + public static void measureAllComponents(GLView parent, int widthSpec, int heightSpec) { + for (int i = 0, n = parent.getComponentCount(); i < n; i++) { + GLView component = parent.getComponent(i); + if (component.getVisibility() == GONE) { + continue; + } + measureComponent(component, widthSpec, heightSpec); + } + } + public void startAnimation(CanvasAnimation animation, boolean atOnce) { GLRoot root = getGLRoot(); if (root == null) throw new IllegalStateException(); @@ -169,6 +304,25 @@ public class GLView implements TouchOwner { invalidate(); } + /** + * Returns the visibility status for this view. + * + * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + */ + @Visibility + @SuppressWarnings("WrongConstant") + public int getVisibility() { + int visibility = mViewFlags & FLAG_INVISIBLE; + + if (visibility == VISIBILITY_INVALID) { + visibility = VISIBLE; + mViewFlags &= ~FLAG_INVISIBLE; + mViewFlags |= visibility; + } + + return visibility; + } + /** * Set the enabled state of this view. * @@ -193,25 +347,6 @@ public class GLView implements TouchOwner { } } - /** - * Returns the visibility status for this view. - * - * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. - */ - @Visibility - @SuppressWarnings("WrongConstant") - public int getVisibility() { - int visibility = mViewFlags & FLAG_INVISIBLE; - - if (visibility == VISIBILITY_INVALID) { - visibility = VISIBLE; - mViewFlags &= ~FLAG_INVISIBLE; - mViewFlags |= visibility; - } - - return visibility; - } - // This should only be called on the content pane (the topmost GLView). void attachToRoot(GLRoot root) { AssertUtils.assertTrue(mParent == null && mRoot == null); @@ -345,6 +480,16 @@ public class GLView implements TouchOwner { component.mParent = null; } + /** + * Returns this GlView's identifier. + * + * @return a positive integer used to identify the view or {@link #NO_ID} + * if the view has no ID + */ + public int getId() { + return mID; + } + /** * Sets the identifier for this GlView. The identifier does not have to be * unique in this GlView's hierarchy. The identifier should be a positive @@ -356,16 +501,6 @@ public class GLView implements TouchOwner { mID = id; } - /** - * Returns this GlView's identifier. - * - * @return a positive integer used to identify the view or {@link #NO_ID} - * if the view has no ID - */ - public int getId() { - return mID; - } - public Rect bounds() { return mBounds; } @@ -451,7 +586,7 @@ public class GLView implements TouchOwner { * @param offset the number of pixels to offset the view by */ public void offsetTopAndBottom(int offset) { - if (offset != 0){ + if (offset != 0) { mBounds.offset(0, offset); dispatchNotifyPositionInRoot(); invalidate(); @@ -464,7 +599,7 @@ public class GLView implements TouchOwner { * @param offset the number of pixels to offset the view by */ public void offsetLeftAndRight(int offset) { - if (offset != 0){ + if (offset != 0) { mBounds.offset(offset, 0); dispatchNotifyPositionInRoot(); invalidate(); @@ -563,7 +698,7 @@ public class GLView implements TouchOwner { } protected boolean dispatchTouchEvent(MotionEvent event, - int x, int y, GLView component, boolean checkBounds) { + int x, int y, GLView component, boolean checkBounds) { Rect rect = component.mBounds; int left = rect.left; int top = rect.top; @@ -769,6 +904,7 @@ public class GLView implements TouchOwner { /** * Get the rect of the view clipped by root rect + * * @param rect the result */ public void getValidRect(Rect rect) { @@ -828,7 +964,6 @@ public class GLView implements TouchOwner { * Returns the minimum height of the view. * * @return the minimum height the view will try to be. - * * @see #setMinimumHeight(int) */ public int getMinimumHeight() { @@ -841,7 +976,6 @@ public class GLView implements TouchOwner { * constrains it with less available height). * * @param minHeight The minimum height the view will try to be. - * * @see #getMinimumHeight() */ public void setMinimumHeight(int minHeight) { @@ -853,7 +987,6 @@ public class GLView implements TouchOwner { * Returns the minimum width of the view. * * @return the minimum width the view will try to be. - * * @see #setMinimumWidth(int) */ public int getMinimumWidth() { @@ -866,7 +999,6 @@ public class GLView implements TouchOwner { * constrains it with less available width). * * @param minWidth The minimum width the view will try to be. - * * @see #getMinimumWidth() */ public void setMinimumWidth(int minWidth) { @@ -892,173 +1024,6 @@ public class GLView implements TouchOwner { boolean changeSize, int left, int top, int right, int bottom) { } - /** - * Utility to return a default begin. Uses the supplied number as left or top. - * - * @param size the parent size - * @param specSize the child size - * @param paddingBeign the padding begin - * @param paddingFinish the padding finish - * @param position the {@link Gravity#POSITION_BEGIN} or {@link Gravity#POSITION_CENTER} or - * {@link Gravity#POSITION_FINISH} - * @return the begin size - */ - public static int getDefaultBegin(int size, int specSize, int paddingBeign, int paddingFinish, - @Gravity.PositionMode int position) { - if (position == Gravity.POSITION_FINISH) { - return size - paddingFinish - specSize; - } else if (position == Gravity.POSITION_CENTER) { - return ((size - paddingBeign - paddingFinish) / 2) - (specSize / 2) + paddingBeign; - } else { - return paddingBeign; - } - } - - /** - * Utility to return a default size. Uses the supplied size if the - * MeasureSpec imposed no constraints. Will get larger if allowed - * by the MeasureSpec. - * - * It works differently from {@link android.view.View#getDefaultSize(int, int)}. - * For {@link MeasureSpec#AT_MOST}, size is suggestion size - * if it is small then spec size or it is not zero, - * otherwise spec size. - * - * @param size Default size for this view - * @param measureSpec Constraints imposed by the parent - * @return The size this view should be. - */ - public static int getDefaultSize(int size, int measureSpec) { - int result = size; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - result = size; - break; - case MeasureSpec.EXACTLY: - result = specSize; - break; - case MeasureSpec.AT_MOST: - return size == 0 ? specSize : Math.min(size, specSize); - } - return result; - } - - /** - * Utility to return a max size. Uses the supplied size if the - * MeasureSpec imposed no constraints. Will get larger if allowed - * by the MeasureSpec. - * - * It works the same as {@link android.view.View#getDefaultSize(int, int)}. - * - * @param size Default size for this view - * @param measureSpec Constraints imposed by the parent - * @return The size this view should be. - */ - public static int getMaxSize(int size, int measureSpec) { - int result = size; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - result = size; - break; - case MeasureSpec.EXACTLY: - case MeasureSpec.AT_MOST: - result = specSize; - break; - } - return result; - } - - public static int getComponentSpec(int spec, int childSize) { - int specMode = MeasureSpec.getMode(spec); - int specSize = MeasureSpec.getSize(spec); - - int size = Math.max(0, specSize); - - int resultSize = 0; - int resultMode = 0; - - switch (specMode) { - // Parent has imposed an exact size on us - case MeasureSpec.EXACTLY: - if (childSize >= 0) { - resultSize = childSize; - resultMode = MeasureSpec.EXACTLY; - } else if (childSize == LayoutParams.MATCH_PARENT) { - // Child wants to be our size. So be it. - resultSize = size; - resultMode = MeasureSpec.EXACTLY; - } else if (childSize == LayoutParams.WRAP_CONTENT) { - // Child wants to determine its own size. It can't be - // bigger than us. - resultSize = size; - resultMode = MeasureSpec.AT_MOST; - } - break; - - // Parent has imposed a maximum size on us - case MeasureSpec.AT_MOST: - if (childSize >= 0) { - // Child wants a specific size... so be it - resultSize = childSize; - resultMode = MeasureSpec.EXACTLY; - } else if (childSize == LayoutParams.MATCH_PARENT) { - // Child wants to be our size, but our size is not fixed. - // Constrain child to not be bigger than us. - resultSize = size; - resultMode = MeasureSpec.AT_MOST; - } else if (childSize == LayoutParams.WRAP_CONTENT) { - // Child wants to determine its own size. It can't be - // bigger than us. - resultSize = size; - resultMode = MeasureSpec.AT_MOST; - } - break; - - // Parent asked to see how big we want to be - case MeasureSpec.UNSPECIFIED: - if (childSize >= 0) { - // Child wants a specific size... let him have it - resultSize = childSize; - resultMode = MeasureSpec.EXACTLY; - } else if (childSize == LayoutParams.MATCH_PARENT) { - // Child wants to be our size... find out how big it should - // be - resultSize = 0; - resultMode = MeasureSpec.UNSPECIFIED; - } else if (childSize == LayoutParams.WRAP_CONTENT) { - // Child wants to determine its own size.... find out how - // big it should be - resultSize = 0; - resultMode = MeasureSpec.UNSPECIFIED; - } - break; - } - return MeasureSpec.makeMeasureSpec(resultSize, resultMode); - } - - public static void measureComponent(GLView component, int widthSpec, int heightSpec) { - final LayoutParams lp = component.getLayoutParams(); - final int componentWidthSpec = getComponentSpec(widthSpec, lp.width); - final int componentHeightSpec = getComponentSpec(heightSpec, lp.height); - component.measure(componentWidthSpec, componentHeightSpec); - } - - public static void measureAllComponents(GLView parent, int widthSpec, int heightSpec) { - for (int i = 0, n = parent.getComponentCount(); i < n; i++) { - GLView component = parent.getComponent(i); - if (component.getVisibility() == GONE) { - continue; - } - measureComponent(component, widthSpec, heightSpec); - } - } - public LayoutParams getLayoutParams() { return mLayoutParams; } @@ -1076,7 +1041,7 @@ public class GLView implements TouchOwner { } protected boolean checkLayoutParams(LayoutParams p) { - return p != null; + return p != null; } protected LayoutParams generateDefaultLayoutParams() { @@ -1159,6 +1124,19 @@ public class GLView implements TouchOwner { } } + @IntDef({VISIBLE, INVISIBLE, GONE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Visibility { + } + + public interface OnClickListener { + boolean onClick(GLView v); + } + + public interface OnLongClickListener { + boolean onLongClick(GLView v); + } + /** * A MeasureSpec encapsulates the layout requirements passed from parent to child. * Each MeasureSpec represents a requirement for either the width or the height. @@ -1182,41 +1160,37 @@ public class GLView implements TouchOwner { * The child can be as large as it wants up to the specified size. * * - * + *

* MeasureSpecs are implemented as ints to reduce object allocation. This class * is provided to pack and unpack the <size, mode> tuple into the int. */ public static class MeasureSpec { private static final int MODE_SHIFT = 30; - private static final int MODE_MASK = 0x3 << MODE_SHIFT; - /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; - /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ - public static final int EXACTLY = 1 << MODE_SHIFT; - + public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ - public static final int AT_MOST = 2 << MODE_SHIFT; - + public static final int AT_MOST = 2 << MODE_SHIFT; + private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * The max size allowed */ - public static final int MAX_SIZE = ~MODE_MASK; + public static final int MAX_SIZE = ~MODE_MASK; /** * Creates a measure specification based on the supplied size and mode. - * + *

* The mode must always be one of the following: *

    *
  • {@link #UNSPECIFIED}
  • @@ -1244,8 +1218,8 @@ public class GLView implements TouchOwner { * * @param measureSpec the measure specification to extract the mode from * @return {@link #UNSPECIFIED}, - * {@link #AT_MOST} or - * {@link #EXACTLY} + * {@link #AT_MOST} or + * {@link #EXACTLY} */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); @@ -1324,10 +1298,10 @@ public class GLView implements TouchOwner { * Creates a new set of layout parameters with the specified width * and height. * - * @param width the width, either {@link #WRAP_CONTENT}, - * {@link #MATCH_PARENT}, or a fixed size in pixels + * @param width the width, either {@link #WRAP_CONTENT}, + * {@link #MATCH_PARENT}, or a fixed size in pixels * @param height the height, either {@link #WRAP_CONTENT}, - * {@link #MATCH_PARENT}, or a fixed size in pixels + * {@link #MATCH_PARENT}, or a fixed size in pixels */ public LayoutParams(int width, int height) { this.width = width; diff --git a/app/src/main/java/com/hippo/glview/view/Gravity.java b/app/src/main/java/com/hippo/glview/view/Gravity.java index 704a6f74a..071537dfc 100644 --- a/app/src/main/java/com/hippo/glview/view/Gravity.java +++ b/app/src/main/java/com/hippo/glview/view/Gravity.java @@ -7,32 +7,19 @@ import java.lang.annotation.RetentionPolicy; public class Gravity { - @IntDef({POSITION_BEGIN, POSITION_CENTER, POSITION_FINISH}) - @Retention(RetentionPolicy.SOURCE) - public @interface PositionMode {} - - @IntDef({HORIZONTAL, VERTICAL}) - @Retention(RetentionPolicy.SOURCE) - public @interface DirectionMode {} - public static final int NO_GRAVITY = 0x0000; - public static final int LEFT = 0x0001; public static final int TOP = 0x0002; public static final int RIGHT = 0x0004; public static final int BOTTOM = 0x0008; - public static final int CENTER_HORIZONTAL = LEFT | RIGHT; - public static final int CENTER_VERTICAL = TOP | BOTTOM; - public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL; - public static final int HORIZONTAL_MASK = CENTER_HORIZONTAL; + public static final int CENTER_VERTICAL = TOP | BOTTOM; + public static final int CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL; public static final int VERTICAL_MASK = CENTER_VERTICAL; - public static final int POSITION_BEGIN = 0; public static final int POSITION_CENTER = 1; public static final int POSITION_FINISH = 2; - public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; @@ -60,7 +47,8 @@ public class Gravity { return (gravity & VERTICAL_MASK) == BOTTOM; } - public static @PositionMode int getPosition(int gravity, @DirectionMode int direction) { + public static @PositionMode + int getPosition(int gravity, @DirectionMode int direction) { if (direction == HORIZONTAL) { if (right(gravity)) { return POSITION_FINISH; @@ -79,4 +67,14 @@ public class Gravity { } } } + + @IntDef({POSITION_BEGIN, POSITION_CENTER, POSITION_FINISH}) + @Retention(RetentionPolicy.SOURCE) + public @interface PositionMode { + } + + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface DirectionMode { + } } diff --git a/app/src/main/java/com/hippo/glview/view/OrientationSource.java b/app/src/main/java/com/hippo/glview/view/OrientationSource.java index 4dd1978bc..6374e3120 100644 --- a/app/src/main/java/com/hippo/glview/view/OrientationSource.java +++ b/app/src/main/java/com/hippo/glview/view/OrientationSource.java @@ -18,5 +18,6 @@ package com.hippo.glview.view; public interface OrientationSource { int getDisplayRotation(); + int getCompensation(); } diff --git a/app/src/main/java/com/hippo/glview/view/TouchHelper.java b/app/src/main/java/com/hippo/glview/view/TouchHelper.java index 0685096cc..f60997951 100644 --- a/app/src/main/java/com/hippo/glview/view/TouchHelper.java +++ b/app/src/main/java/com/hippo/glview/view/TouchHelper.java @@ -27,29 +27,26 @@ import com.hippo.yorozuya.SimpleHandler; public class TouchHelper { + /** + * Cache the touch slop from the context that created the view. + */ + private static int sTouchSlop = 8; private final TouchOwner mOwner; - private boolean mPrePressed = false; private boolean mHasPerformedLongPress = false; - private CheckForLongPress mPendingCheckForLongPress = null; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick = null; private UnsetPressedState mUnsetPressedState = null; - /** - * Cache the touch slop from the context that created the view. - */ - private static int sTouchSlop = 8; + public TouchHelper(@NonNull TouchOwner owner) { + mOwner = owner; + } public static void initialize(Context context) { sTouchSlop = LayoutUtils.dp2pix(context, 8); // 8dp } - public TouchHelper(@NonNull TouchOwner owner) { - mOwner = owner; - } - public boolean onTouch(MotionEvent event) { TouchOwner owner = mOwner; final float x = event.getX(); @@ -152,7 +149,7 @@ public class TouchHelper { return false; } - private void setPressed(boolean pressed, float x, float y){ + private void setPressed(boolean pressed, float x, float y) { mOwner.setPressed(pressed); if (pressed) { mOwner.setHotspot(x, y); diff --git a/app/src/main/java/com/hippo/glview/widget/GLEdgeEffect.java b/app/src/main/java/com/hippo/glview/widget/GLEdgeEffect.java index 5b0d1c232..e2154dd5c 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLEdgeEffect.java +++ b/app/src/main/java/com/hippo/glview/widget/GLEdgeEffect.java @@ -53,35 +53,25 @@ public class GLEdgeEffect { private static final double ANGLE = Math.PI / 6; private static final float SIN = (float) Math.sin(ANGLE); private static final float COS = (float) Math.cos(ANGLE); - - private float mGlowAlpha; - private float mGlowScaleY; - - private float mGlowAlphaStart; - private float mGlowAlphaFinish; - private float mGlowScaleYStart; - private float mGlowScaleYFinish; - - private long mStartTime; - private float mDuration; - - private final Interpolator mInterpolator; - private static final int STATE_IDLE = 0; private static final int STATE_PULL = 1; private static final int STATE_ABSORB = 2; private static final int STATE_RECEDE = 3; private static final int STATE_PULL_DECAY = 4; - private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f; - private static final int VELOCITY_GLOW_FACTOR = 6; - - private int mState = STATE_IDLE; - - private float mPullDistance; - + private final Interpolator mInterpolator; private final Rect mBounds = new Rect(); + private float mGlowAlpha; + private float mGlowScaleY; + private float mGlowAlphaStart; + private float mGlowAlphaFinish; + private float mGlowScaleYStart; + private float mGlowScaleYFinish; + private long mStartTime; + private float mDuration; + private int mState = STATE_IDLE; + private float mPullDistance; private int mColor; private float mRadius; private float mBaseGlowScale; @@ -99,7 +89,7 @@ public class GLEdgeEffect { /** * Set the size of this edge effect in pixels. * - * @param width Effect width in pixels + * @param width Effect width in pixels * @param height Effect height in pixels */ public void setSize(int width, int height) { @@ -161,9 +151,9 @@ public class GLEdgeEffect { * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. - * @param displacement The displacement from the starting side of the effect of the point - * initiating the pull. In the case of touch this is the finger position. - * Values may be from 0-1. + * @param displacement The displacement from the starting side of the effect of the point + * initiating the pull. In the case of touch this is the finger position. + * Values may be from 0-1. */ public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); @@ -256,6 +246,15 @@ public class GLEdgeEffect { mTargetDisplacement = 0.5f; } + /** + * Return the color of this edge effect in argb. + * + * @return The color of this edge effect in argb + */ + public int getColor() { + return mColor; + } + /** * Set the color of this edge effect in argb. * @@ -265,14 +264,6 @@ public class GLEdgeEffect { mColor = color; } - /** - * Return the color of this edge effect in argb. - * @return The color of this edge effect in argb - */ - public int getColor() { - return mColor; - } - /** * Draw into the provided canvas. Assumes that the canvas has been rotated * accordingly and the size has been set. The effect will be drawn the full @@ -281,7 +272,7 @@ public class GLEdgeEffect { * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the - * animation + * animation */ public boolean draw(GLCanvas canvas) { update(); @@ -314,6 +305,7 @@ public class GLEdgeEffect { /** * Return the maximum height that the edge effect will be drawn at given the original * {@link #setSize(int, int) input size}. + * * @return The maximum height of the edge effect */ public int getMaxHeight() { diff --git a/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java b/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java index 51a35546c..18bc108f7 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java +++ b/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java @@ -28,15 +28,10 @@ import java.lang.annotation.RetentionPolicy; public final class GLEdgeView extends GLView { - @IntDef({TOP, LEFT, BOTTOM, RIGHT}) - @Retention(RetentionPolicy.SOURCE) - private @interface Direction {} - public static final int TOP = 0; public static final int LEFT = 1; public static final int BOTTOM = 2; public static final int RIGHT = 3; - // Each edge effect has a transform matrix, and each matrix has 16 elements. // We put all the elements in one array. These constants specify the // starting index of each matrix. @@ -44,10 +39,8 @@ public final class GLEdgeView extends GLView { private static final int LEFT_M = LEFT * 16; private static final int BOTTOM_M = BOTTOM * 16; private static final int RIGHT_M = RIGHT * 16; - private final GLEdgeEffect[] mEffect = new GLEdgeEffect[4]; private final float[] mMatrix = new float[4 * 16]; - public GLEdgeView(int color) { for (int i = 0; i < 4; i++) { mEffect[i] = new GLEdgeEffect(color); @@ -160,4 +153,9 @@ public final class GLEdgeView extends GLView { public boolean isFinished(int direction) { return mEffect[direction].isFinished(); } + + @IntDef({TOP, LEFT, BOTTOM, RIGHT}) + @Retention(RetentionPolicy.SOURCE) + private @interface Direction { + } } diff --git a/app/src/main/java/com/hippo/glview/widget/GLLinearLayout.java b/app/src/main/java/com/hippo/glview/widget/GLLinearLayout.java index f1525c374..1cd4ff64e 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLLinearLayout.java +++ b/app/src/main/java/com/hippo/glview/widget/GLLinearLayout.java @@ -30,18 +30,12 @@ import java.util.List; public class GLLinearLayout extends GLView { - @IntDef({HORIZONTAL, VERTICAL}) - @Retention(RetentionPolicy.SOURCE) - public @interface OrientationMode {} - public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; - + private final List mTempList = new ArrayList<>(); private int mInterval = 0; private int mOrientation = VERTICAL; - private final List mTempList = new ArrayList<>(); - public void setInterval(int interval) { if (mInterval != interval) { mInterval = interval; @@ -51,8 +45,9 @@ public class GLLinearLayout extends GLView { /** * Should the layout be a column or a row. + * * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default - * value is {@link #HORIZONTAL}. + * value is {@link #HORIZONTAL}. */ public void setOrientation(@OrientationMode int orientation) { if (mOrientation != orientation) { @@ -224,6 +219,11 @@ public class GLLinearLayout extends GLView { return p == null ? generateDefaultLayoutParams() : new LayoutParams(p); } + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface OrientationMode { + } + public static class LayoutParams extends GravityLayoutParams { public float weight = 0.0f; diff --git a/app/src/main/java/com/hippo/glview/widget/GLProgressView.java b/app/src/main/java/com/hippo/glview/widget/GLProgressView.java index 3a8467632..175cb6316 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLProgressView.java +++ b/app/src/main/java/com/hippo/glview/widget/GLProgressView.java @@ -18,10 +18,11 @@ package com.hippo.glview.widget; import android.graphics.Color; import android.graphics.Path; -import androidx.core.view.animation.PathInterpolatorCompat; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; +import androidx.core.view.animation.PathInterpolatorCompat; + import com.hippo.glview.anim.Animation; import com.hippo.glview.anim.FloatAnimation; import com.hippo.glview.glrenderer.GLCanvas; @@ -148,7 +149,7 @@ public class GLProgressView extends GLView { @Override protected void onLayout(boolean changeSize, int left, int top, int right, - int bottom) { + int bottom) { super.onLayout(changeSize, left, top, right, bottom); int width = right - left; @@ -170,6 +171,10 @@ public class GLProgressView extends GLView { invalidate(); } + public boolean isIndeterminate() { + return mIndeterminate; + } + public void setIndeterminate(boolean indeterminate) { if (mIndeterminate != indeterminate) { mIndeterminate = indeterminate; @@ -182,10 +187,6 @@ public class GLProgressView extends GLView { } } - public boolean isIndeterminate() { - return mIndeterminate; - } - public void setProgress(float progress) { if (!mIndeterminate) { mTrimStart = 0.0f; diff --git a/app/src/main/java/com/hippo/glview/widget/GLTextureView.java b/app/src/main/java/com/hippo/glview/widget/GLTextureView.java index d24bfb153..240d19230 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLTextureView.java +++ b/app/src/main/java/com/hippo/glview/widget/GLTextureView.java @@ -29,6 +29,10 @@ public class GLTextureView extends GLView { private Texture mTexture; + public Texture getTexture() { + return mTexture; + } + public void setTexture(Texture texture) { mTexture = texture; if (texture != null) { @@ -39,10 +43,6 @@ public class GLTextureView extends GLView { invalidate(); } - public Texture getTexture() { - return mTexture; - } - @Override protected int getSuggestedMinimumWidth() { return mTexture == null ? super.getSuggestedMinimumWidth() : mTexture.getWidth() + mPaddings.width(); diff --git a/app/src/main/java/com/hippo/widget/BatteryView.java b/app/src/main/java/com/hippo/widget/BatteryView.java index 7e5fbc555..4b2f94610 100644 --- a/app/src/main/java/com/hippo/widget/BatteryView.java +++ b/app/src/main/java/com/hippo/widget/BatteryView.java @@ -51,20 +51,7 @@ public class BatteryView extends AppCompatTextView { public BatteryView(Context context, AttributeSet attrs) { this(context, attrs, 0); - } private final Runnable mCharger = new Runnable() { - - private int level = 0; - - @Override - public void run() { - level += 2; - if (level > 100) { - level = 0; - } - mDrawable.setElect(level, false); - getHandler().postDelayed(mCharger, 200); - } - }; + } public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -79,7 +66,20 @@ public class BatteryView extends AppCompatTextView { mDrawable.setColor(mColor); mDrawable.setWarningColor(mWarningColor); - } + } private final Runnable mCharger = new Runnable() { + + private int level = 0; + + @Override + public void run() { + level += 2; + if (level > 100) { + level = 0; + } + mDrawable.setElect(level, false); + getHandler().postDelayed(mCharger, 200); + } + }; private void init() { mDrawable = new BatteryDrawable(); @@ -120,6 +120,23 @@ public class BatteryView extends AppCompatTextView { registerReceiver(); } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { + unregisterReceiver(); + stopCharger(); + mAttached = false; + } + } + + private void registerReceiver() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); } private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -151,23 +168,6 @@ public class BatteryView extends AppCompatTextView { } }; - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (mAttached) { - unregisterReceiver(); - stopCharger(); - mAttached = false; - } - } - - private void registerReceiver() { - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); - } - private void unregisterReceiver() { getContext().unregisterReceiver(mIntentReceiver); } diff --git a/app/src/main/java/com/hippo/widget/ContentLayout.java b/app/src/main/java/com/hippo/widget/ContentLayout.java index d047a482f..58842e658 100644 --- a/app/src/main/java/com/hippo/widget/ContentLayout.java +++ b/app/src/main/java/com/hippo/widget/ContentLayout.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; -import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; diff --git a/app/src/main/java/com/hippo/widget/TextClock.java b/app/src/main/java/com/hippo/widget/TextClock.java index 762435b68..d7c2d4959 100644 --- a/app/src/main/java/com/hippo/widget/TextClock.java +++ b/app/src/main/java/com/hippo/widget/TextClock.java @@ -73,6 +73,16 @@ public class TextClock extends AppCompatTextView { 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 final Runnable mTicker = new Runnable() { @Override public void run() { @@ -85,16 +95,6 @@ public class TextClock extends AppCompatTextView { } }; - 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 @@ -111,6 +111,17 @@ public class TextClock extends AppCompatTextView { public CharSequence getFormat12Hour() { return mFormat12; + } + + public void setFormat12Hour(CharSequence format) { + mFormat12 = format; + + chooseFormat(); + onTimeChanged(); + } + + public CharSequence getFormat24Hour() { + return mFormat24; } private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -125,17 +136,6 @@ public class TextClock extends AppCompatTextView { } }; - public void setFormat12Hour(CharSequence format) { - mFormat12 = format; - - chooseFormat(); - onTimeChanged(); - } - - public CharSequence getFormat24Hour() { - return mFormat24; - } - public void setFormat24Hour(CharSequence format) { mFormat24 = format; diff --git a/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java b/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java index 8c3b02dab..126fede77 100644 --- a/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java @@ -23,10 +23,9 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; public final class AnimationUtils { - private AnimationUtils() { - } - public static final Interpolator FAST_SLOW_INTERPOLATOR = new LinearOutSlowInInterpolator(); public static final Interpolator SLOW_FAST_INTERPOLATOR = new FastOutLinearInInterpolator(); public static final Interpolator SLOW_FAST_SLOW_INTERPOLATOR = new FastOutSlowInInterpolator(); + private AnimationUtils() { + } } diff --git a/app/src/main/java/com/hippo/yorozuya/AssertUtils.java b/app/src/main/java/com/hippo/yorozuya/AssertUtils.java index c75eda610..b8b185748 100644 --- a/app/src/main/java/com/hippo/yorozuya/AssertUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/AssertUtils.java @@ -17,7 +17,8 @@ package com.hippo.yorozuya; public final class AssertUtils { - private AssertUtils() {} + private AssertUtils() { + } public static void assertTrue(boolean cond) { assertTrue("Condition has to be true.", cond); diff --git a/app/src/main/java/com/hippo/yorozuya/ColorUtils.java b/app/src/main/java/com/hippo/yorozuya/ColorUtils.java index 2edbb7f17..a78306968 100644 --- a/app/src/main/java/com/hippo/yorozuya/ColorUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/ColorUtils.java @@ -17,7 +17,8 @@ package com.hippo.yorozuya; public final class ColorUtils { - private ColorUtils() {} + private ColorUtils() { + } /** * Check is it the color opaque diff --git a/app/src/main/java/com/hippo/yorozuya/FileUtils.java b/app/src/main/java/com/hippo/yorozuya/FileUtils.java index d0e2e4184..4b1cc0bec 100644 --- a/app/src/main/java/com/hippo/yorozuya/FileUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/FileUtils.java @@ -30,7 +30,20 @@ import java.io.OutputStream; import java.util.Locale; public final class FileUtils { - private FileUtils() {} + private static final char[] FORBIDDEN_FILENAME_CHARACTERS = { + '\\', + '/', + ':', + '*', + '?', + '"', + '<', + '>', + '|', + }; + + private FileUtils() { + } public static boolean ensureFile(File file) { return file != null && (!file.exists() || file.isFile()); @@ -53,14 +66,14 @@ public final class FileUtils { * http://stackoverflow.com/questions/3758606/ * * @param bytes the bytes to convert - * @param si si units + * @param si si units * @return the human readable string */ public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); return String.format(Locale.US, "%.1f %sB", bytes / Math.pow(unit, exp), pre); } @@ -68,7 +81,7 @@ public final class FileUtils { * Try to delete file, dir and it's children * * @param file the file to delete - * The dir to deleted + * The dir to deleted */ public static boolean delete(File file) { if (file == null) { @@ -151,18 +164,6 @@ public final class FileUtils { } } - private static final char[] FORBIDDEN_FILENAME_CHARACTERS = { - '\\', - '/', - ':', - '*', - '?', - '"', - '<', - '>', - '|', - }; - public static String sanitizeFilename(@NonNull String filename) { // Remove forbidden_filename_characters filename = StringUtils.remove(filename, FORBIDDEN_FILENAME_CHARACTERS); @@ -235,7 +236,7 @@ public final class FileUtils { /** * Create a temp file, you need to delete it by you self. * - * @param parent The temp file's parent + * @param parent The temp file's parent * @param extension The extension of temp file * @return The temp file or null */ diff --git a/app/src/main/java/com/hippo/yorozuya/IOUtils.java b/app/src/main/java/com/hippo/yorozuya/IOUtils.java index 79f52a939..259aff7f0 100644 --- a/app/src/main/java/com/hippo/yorozuya/IOUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/IOUtils.java @@ -25,12 +25,12 @@ import java.io.InputStreamReader; import java.io.OutputStream; public final class IOUtils { - private IOUtils() {} - private static final int EOF = -1; - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + private IOUtils() { + } + /** * Close the closeable stuff. Don't worry about anything. * @@ -50,7 +50,7 @@ public final class IOUtils { * Copy bytes from an InputStream to an * OutputStream. * - * @param input the InputStream + * @param input the InputStream * @param output the OutputStream * @return the number of bytes copied * @throws IOException @@ -72,7 +72,7 @@ public final class IOUtils { * "\n". * * @throws java.io.EOFException if the stream is exhausted before the next - * newline character. + * newline character. */ public static String readAsciiLine(final InputStream in) throws IOException { final StringBuilder result = new StringBuilder(80); @@ -84,7 +84,7 @@ public final class IOUtils { break; } - result.append((char)c); + result.append((char) c); } final int length = result.length(); if (length > 0 && result.charAt(length - 1) == '\r') { diff --git a/app/src/main/java/com/hippo/yorozuya/IntIdGenerator.java b/app/src/main/java/com/hippo/yorozuya/IntIdGenerator.java index 6250a33db..48e58cc6b 100644 --- a/app/src/main/java/com/hippo/yorozuya/IntIdGenerator.java +++ b/app/src/main/java/com/hippo/yorozuya/IntIdGenerator.java @@ -24,7 +24,8 @@ public final class IntIdGenerator { private final AtomicInteger mId = new AtomicInteger(); - public IntIdGenerator() {} + public IntIdGenerator() { + } public IntIdGenerator(int init) { setNextId(init); @@ -33,7 +34,7 @@ public final class IntIdGenerator { @SuppressWarnings("StatementWithEmptyBody") public int nextId() { int id; - while ((id = mId.getAndIncrement()) == INVALID_ID); + while ((id = mId.getAndIncrement()) == INVALID_ID) ; return id; } diff --git a/app/src/main/java/com/hippo/yorozuya/LayoutUtils.java b/app/src/main/java/com/hippo/yorozuya/LayoutUtils.java index 2cca6dccf..214114d23 100644 --- a/app/src/main/java/com/hippo/yorozuya/LayoutUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/LayoutUtils.java @@ -19,13 +19,14 @@ package com.hippo.yorozuya; import android.content.Context; public final class LayoutUtils { - private LayoutUtils() {} + private LayoutUtils() { + } /** * dp conversion to pix * * @param context The context - * @param dp The value you want to conversion + * @param dp The value you want to conversion * @return value in pix */ public static int dp2pix(Context context, float dp) { @@ -36,7 +37,7 @@ public final class LayoutUtils { * pix conversion to dp * * @param context The context - * @param pix The value you want to conversion + * @param pix The value you want to conversion * @return value in dp */ public static float pix2dp(Context context, int pix) { diff --git a/app/src/main/java/com/hippo/yorozuya/LongIdGenerator.java b/app/src/main/java/com/hippo/yorozuya/LongIdGenerator.java index 20b62530c..0b89fc8bf 100644 --- a/app/src/main/java/com/hippo/yorozuya/LongIdGenerator.java +++ b/app/src/main/java/com/hippo/yorozuya/LongIdGenerator.java @@ -24,7 +24,8 @@ public final class LongIdGenerator { private final AtomicLong mId = new AtomicLong(); - public LongIdGenerator() {} + public LongIdGenerator() { + } public LongIdGenerator(long init) { setNextId(init); @@ -33,7 +34,7 @@ public final class LongIdGenerator { @SuppressWarnings("StatementWithEmptyBody") public long nextId() { long id; - while ((id = mId.getAndIncrement()) == INVALID_ID); + while ((id = mId.getAndIncrement()) == INVALID_ID) ; return id; } diff --git a/app/src/main/java/com/hippo/yorozuya/MathUtils.java b/app/src/main/java/com/hippo/yorozuya/MathUtils.java index 05b9dfa3d..669f6d5fc 100644 --- a/app/src/main/java/com/hippo/yorozuya/MathUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/MathUtils.java @@ -20,11 +20,11 @@ import java.util.Random; // Get most code from android.util.MathUtils public final class MathUtils { - private MathUtils() {} - private static final Random sRandom = new Random(); private static final float DEG_TO_RAD = 3.1415926f / 180.0f; private static final float RAD_TO_DEG = 180.0f / 3.1415926f; + private MathUtils() { + } public static float abs(float v) { return v > 0 ? v : -v; @@ -285,7 +285,7 @@ public final class MathUtils { * Throws IllegalArgumentException if the input is <= 0 or * the answer overflows. */ - public static int previousPowerOf2 (int n) { + public static int previousPowerOf2(int n) { if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n); n |= n >> 1; n |= n >> 2; diff --git a/app/src/main/java/com/hippo/yorozuya/Messenger.java b/app/src/main/java/com/hippo/yorozuya/Messenger.java index 1e217ebc1..b3d760a6e 100644 --- a/app/src/main/java/com/hippo/yorozuya/Messenger.java +++ b/app/src/main/java/com/hippo/yorozuya/Messenger.java @@ -24,10 +24,14 @@ import java.util.List; public final class Messenger { + private static Messenger sInstance; private final IntIdGenerator mIdGenerator; private final SparseArray> mReceiverListMap; - private static Messenger sInstance; + private Messenger() { + mIdGenerator = new IntIdGenerator(); + mReceiverListMap = new SparseArray<>(); + } public static Messenger getInstance() { if (sInstance == null) { @@ -36,11 +40,6 @@ public final class Messenger { return sInstance; } - private Messenger() { - mIdGenerator = new IntIdGenerator(); - mReceiverListMap = new SparseArray<>(); - } - public int newId() { return mIdGenerator.nextId(); } diff --git a/app/src/main/java/com/hippo/yorozuya/NumberUtils.java b/app/src/main/java/com/hippo/yorozuya/NumberUtils.java index 383979ae0..74175c999 100644 --- a/app/src/main/java/com/hippo/yorozuya/NumberUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/NumberUtils.java @@ -17,7 +17,8 @@ package com.hippo.yorozuya; public final class NumberUtils { - private NumberUtils() {} + private NumberUtils() { + } /** * 0 for false, Non 0 for true @@ -42,7 +43,7 @@ public final class NumberUtils { /** * Do not throw NumberFormatException, use default value * - * @param str the string to be parsed + * @param str the string to be parsed * @param defaultValue the value to return when get error * @return the value of the string */ @@ -57,7 +58,7 @@ public final class NumberUtils { /** * Do not throw NumberFormatException, use default value * - * @param str the string to be parsed + * @param str the string to be parsed * @param defaultValue the value to return when get error * @return the value of the string */ @@ -72,7 +73,7 @@ public final class NumberUtils { /** * Do not throw NumberFormatException, use default value * - * @param str the string to be parsed + * @param str the string to be parsed * @param defaultValue the value to return when get error * @return the value of the string */ diff --git a/app/src/main/java/com/hippo/yorozuya/OSUtils.java b/app/src/main/java/com/hippo/yorozuya/OSUtils.java index d95a73739..703a658f9 100644 --- a/app/src/main/java/com/hippo/yorozuya/OSUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/OSUtils.java @@ -25,15 +25,15 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public final class OSUtils { - private OSUtils() {} - private static final String PROCFS_MEMFILE = "/proc/meminfo"; private static final Pattern PROCFS_MEMFILE_FORMAT = Pattern.compile("^([a-zA-Z]*):[ \t]*([0-9]*)[ \t]kB"); private static final String MEMTOTAL_STRING = "MemTotal"; - private static long sTotalMem = Long.MIN_VALUE; + private OSUtils() { + } + public static void checkMainLoop() { if (Looper.myLooper() != Looper.getMainLooper()) { throw new IllegalStateException("It is in not main loop!"); diff --git a/app/src/main/java/com/hippo/yorozuya/ObjectUtils.java b/app/src/main/java/com/hippo/yorozuya/ObjectUtils.java index 9e38375fa..904ddb54e 100644 --- a/app/src/main/java/com/hippo/yorozuya/ObjectUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/ObjectUtils.java @@ -26,7 +26,8 @@ import java.util.Map; import java.util.Set; public final class ObjectUtils { - private ObjectUtils() {} + private ObjectUtils() { + } /** * Returns true if two possibly-null objects are equal. @@ -43,39 +44,57 @@ public final class ObjectUtils { } private static void dumpObject(Object o, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(o.getClass().getName());writer.write('\n'); + writer.write(prefix); + writer.write(o.getClass().getName()); + writer.write('\n'); } private static void dumpBoolean(boolean z, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Boolean.toString(z));writer.write('\n'); + writer.write(prefix); + writer.write(Boolean.toString(z)); + writer.write('\n'); } private static void dumpByte(byte b, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Byte.toString(b));writer.write('\n'); + writer.write(prefix); + writer.write(Byte.toString(b)); + writer.write('\n'); } private static void dumpChar(char c, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Character.toString(c));writer.write('\n'); + writer.write(prefix); + writer.write(Character.toString(c)); + writer.write('\n'); } private static void dumpShort(short s, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Short.toString(s));writer.write('\n'); + writer.write(prefix); + writer.write(Short.toString(s)); + writer.write('\n'); } private static void dumpInt(int i, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Integer.toString(i));writer.write('\n'); + writer.write(prefix); + writer.write(Integer.toString(i)); + writer.write('\n'); } private static void dumpLong(long j, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Long.toString(j));writer.write('\n'); + writer.write(prefix); + writer.write(Long.toString(j)); + writer.write('\n'); } private static void dumpFloat(float f, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Float.toString(f));writer.write('\n'); + writer.write(prefix); + writer.write(Float.toString(f)); + writer.write('\n'); } private static void dumpDouble(double d, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(Double.toString(d));writer.write('\n'); + writer.write(prefix); + writer.write(Double.toString(d)); + writer.write('\n'); } private static void dumpArray(Object array, PrintWriter writer, String prefix, boolean skipFirstPrefix) { @@ -86,7 +105,9 @@ public final class ObjectUtils { } String newPrefix = prefix + " "; - writer.write(newPrefix);writer.write('[');writer.write('\n'); + writer.write(newPrefix); + writer.write('['); + writer.write('\n'); if (array instanceof Object[]) { Object[] a = (Object[]) array; @@ -145,13 +166,18 @@ public final class ObjectUtils { Map map = (Map) array; for (Object key : map.keySet()) { Object value = map.get(key); - writer.write(newPrefix);writer.write(key.toString());writer.write(": ");dump(value, writer, newPrefix, true); + writer.write(newPrefix); + writer.write(key.toString()); + writer.write(": "); + dump(value, writer, newPrefix, true); } } else { throw new IllegalStateException(array + " is not array"); } - writer.write(newPrefix);writer.write(']');writer.write('\n'); + writer.write(newPrefix); + writer.write(']'); + writer.write('\n'); } public static void dump(Object o, PrintWriter writer) { @@ -196,7 +222,10 @@ public final class ObjectUtils { String name = field.getName(); try { Object value = field.get(o); - writer.write(newPrefix);writer.write(name);writer.write(": ");dump(value, writer, newPrefix, true); + writer.write(newPrefix); + writer.write(name); + writer.write(": "); + dump(value, writer, newPrefix, true); } catch (IllegalAccessException e) { // Ignore } diff --git a/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java b/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java index 2cc6c6733..d425386cc 100644 --- a/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java @@ -23,10 +23,10 @@ import android.util.TypedValue; import androidx.annotation.AttrRes; public final class ResourcesUtils { - private ResourcesUtils() {} - private static final Object mAccessLock = new Object(); private static TypedValue mTmpValue = new TypedValue(); + private ResourcesUtils() { + } public static float getFloat(Resources resources, int resId) { TypedValue outValue = new TypedValue(); diff --git a/app/src/main/java/com/hippo/yorozuya/SimpleHandler.java b/app/src/main/java/com/hippo/yorozuya/SimpleHandler.java index 1cb6fa94b..5937a3636 100644 --- a/app/src/main/java/com/hippo/yorozuya/SimpleHandler.java +++ b/app/src/main/java/com/hippo/yorozuya/SimpleHandler.java @@ -23,14 +23,14 @@ public final class SimpleHandler extends Handler { private static Handler sInstance; + private SimpleHandler(Looper mainLooper) { + super(mainLooper); + } + public static Handler getInstance() { if (sInstance == null) { sInstance = new Handler(Looper.getMainLooper()); } return sInstance; } - - private SimpleHandler(Looper mainLooper) { - super(mainLooper); - } } diff --git a/app/src/main/java/com/hippo/yorozuya/StringUtils.java b/app/src/main/java/com/hippo/yorozuya/StringUtils.java index 896f1fed0..2313d8e95 100644 --- a/app/src/main/java/com/hippo/yorozuya/StringUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/StringUtils.java @@ -23,10 +23,13 @@ import java.util.Arrays; import java.util.List; public final class StringUtils { - private StringUtils() {} - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - + public static final char[] WHITE_SPACE_ARRAY = { + '\u0009', // TAB + '\u0020', // SPACE + '\u00A0', // NO-BREAK SPACE + '\u3000', // IDEOGRAPHIC SPACE + }; private static final String[] ESCAPE_CHARATER_LIST = { "&", "<", @@ -47,6 +50,9 @@ public final class StringUtils { " " }; + private StringUtils() { + } + /** * Unescape xml. It do not work perfectly. */ @@ -70,12 +76,12 @@ public final class StringUtils { * StringUtils.replace("aba", "a", "z") = "zbz" * * - * @see #replace(String text, String searchString, String replacement, int max) - * @param text text to search and replace in, may be null - * @param searchString the String to search for, may be null + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null * @param replacement the String to replace it with, may be null * @return the text with any replacements processed, - * {@code null} if null String input + * {@code null} if null String input + * @see #replace(String text, String searchString, String replacement, int max) */ public static String replace(final String text, final String searchString, final String replacement) { return replace(text, searchString, replacement, -1); @@ -102,12 +108,12 @@ public final class StringUtils { * StringUtils.replace("abaa", "a", "z", -1) = "zbzz" * * - * @param text text to search and replace in, may be null - * @param searchString the String to search for, may be null + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null * @param replacement the String to replace it with, may be null - * @param max maximum number of values to replace, or {@code -1} if no maximum + * @param max maximum number of values to replace, or {@code -1} if no maximum * @return the text with any replacements processed, - * {@code null} if null String input + * {@code null} if null String input */ public static String replace(final String text, final String searchString, final String replacement, int max) { if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchString) || replacement == null || max == 0) { @@ -161,21 +167,17 @@ public final class StringUtils { * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte" * * - * @param text - * text to search and replace in, no-op if null - * @param searchList - * the Strings to search for, no-op if null - * @param replacementList - * the Strings to replace them with, no-op if null + * @param text text to search and replace in, no-op if null + * @param searchList the Strings to search for, no-op if null + * @param replacementList the Strings to replace them with, no-op if null * @return the text with any replacements processed, {@code null} if - * null String input - * @throws IllegalArgumentException - * if the lengths of the arrays are not the same (null is ok, - * and/or size 0) + * null String input + * @throws IllegalArgumentException if the lengths of the arrays are not the same (null is ok, + * and/or size 0) */ // Get from org.apache.commons.lang3.StringUtils public static String replaceEach(final String text, final String[] searchList, - final String[] replacementList) { + final String[] replacementList) { return replaceEach(text, searchList, replacementList, false, 0); } @@ -206,25 +208,19 @@ public final class StringUtils { * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException * * - * @param text - * text to search and replace in, no-op if null - * @param searchList - * the Strings to search for, no-op if null - * @param replacementList - * the Strings to replace them with, no-op if null - * @param repeat if true, then replace repeatedly - * until there are no more possible replacements or timeToLive < 0 - * @param timeToLive - * if less than 0 then there is a circular reference and endless - * loop + * @param text text to search and replace in, no-op if null + * @param searchList the Strings to search for, no-op if null + * @param replacementList the Strings to replace them with, no-op if null + * @param repeat if true, then replace repeatedly + * until there are no more possible replacements or timeToLive < 0 + * @param timeToLive if less than 0 then there is a circular reference and endless + * loop * @return the text with any replacements processed, {@code null} if - * null String input - * @throws IllegalStateException - * if the search is repeating and there is an endless loop due - * to outputs of one being inputs to another - * @throws IllegalArgumentException - * if the lengths of the arrays are not the same (null is ok, - * and/or size 0) + * null String input + * @throws IllegalStateException if the search is repeating and there is an endless loop due + * to outputs of one being inputs to another + * @throws IllegalArgumentException if the lengths of the arrays are not the same (null is ok, + * and/or size 0) */ // Get from org.apache.commons.lang3.StringUtils private static String replaceEach( @@ -385,8 +381,8 @@ public final class StringUtils { * StringUtils.split("a b c", ' ') = ["a", "b", "c"] * * - * @param str the String to parse, may be null - * @param separatorChar the character used as the delimiter + * @param str the String to parse, may be null + * @param separatorChar the character used as the delimiter * @return an array of parsed Strings, {@code null} if null String input * @since 2.0 */ @@ -400,11 +396,11 @@ public final class StringUtils { * {@code splitPreserveAllTokens} methods that do not return a * maximum array length. * - * @param str the String to parse, may be {@code null} - * @param separatorChar the separate character + * @param str the String to parse, may be {@code null} + * @param separatorChar the separate character * @param preserveAllTokens if {@code true}, adjacent separators are - * treated as empty token separators; if {@code false}, adjacent - * separators are treated as one separator. + * treated as empty token separators; if {@code false}, adjacent + * separators are treated as one separator. * @return an array of parsed Strings, {@code null} if null String input */ // Get from org.apache.commons.lang3.StringUtils @@ -482,7 +478,7 @@ public final class StringUtils { * StringUtils.countMatches("abba", 'x') = 0 * * - * @param str the CharSequence to check, may be null + * @param str the CharSequence to check, may be null * @param ch the char to count * @return the number of occurrences, 0 if the CharSequence is {@code null} */ @@ -521,13 +517,6 @@ public final class StringUtils { return pos; } - public static final char[] WHITE_SPACE_ARRAY = { - '\u0009', // TAB - '\u0020', // SPACE - '\u00A0', // NO-BREAK SPACE - '\u3000', // IDEOGRAPHIC SPACE - }; - /** * Works like {@link String#trim()}, but more white space is excluded. * The white space characters is {@link #WHITE_SPACE_ARRAY}. diff --git a/app/src/main/java/com/hippo/yorozuya/Utilities.java b/app/src/main/java/com/hippo/yorozuya/Utilities.java index 4bb4486d0..c0f2232d5 100644 --- a/app/src/main/java/com/hippo/yorozuya/Utilities.java +++ b/app/src/main/java/com/hippo/yorozuya/Utilities.java @@ -25,11 +25,18 @@ import java.util.regex.Pattern; public final class Utilities { + private static final String[] MIME_TYPE_ARRAY = { + "image/jpg" + }; + private static final String[] EXTENSION_ARRAY = { + "jpg" + }; + /** * Whether the array contain the element * * @param array the array - * @param obj the element + * @param obj the element * @return true for the array contain the element */ public static boolean contain(@Nullable Object[] array, @Nullable Object obj) { @@ -49,7 +56,7 @@ public final class Utilities { * Whether the array contain the element * * @param array the array - * @param ch the element + * @param ch the element * @return true for the array contain the element */ public static boolean contain(@Nullable char[] array, char ch) { @@ -57,7 +64,7 @@ public final class Utilities { return false; } - for (char c: array) { + for (char c : array) { if (c == ch) { return true; } @@ -95,14 +102,6 @@ public final class Utilities { return null; } - private static final String[] MIME_TYPE_ARRAY = { - "image/jpg" - }; - - private static final String[] EXTENSION_ARRAY = { - "jpg" - }; - public static String getExtensionFromMimeType(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return null; diff --git a/app/src/main/java/com/hippo/yorozuya/ViewUtils.java b/app/src/main/java/com/hippo/yorozuya/ViewUtils.java index bad588f9a..2106e96d4 100644 --- a/app/src/main/java/com/hippo/yorozuya/ViewUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/ViewUtils.java @@ -35,16 +35,16 @@ import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; public final class ViewUtils { - private ViewUtils() {} - public static final int MAX_SIZE = Integer.MAX_VALUE & ~(0x3 << 30); - private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); + private ViewUtils() { + } + /** * Get view center location in window * - * @param view the view to check + * @param view the view to check * @param location an array of two integers in which to hold the coordinates */ public static void getCenterInWindows(View view, int[] location) { @@ -56,7 +56,7 @@ public final class ViewUtils { /** * Get view location in window * - * @param view the view to check + * @param view the view to check * @param location an array of two integers in which to hold the coordinates */ public static void getLocationInWindow(View view, int[] location) { @@ -66,8 +66,8 @@ public final class ViewUtils { /** * Get view center in ths ancestor * - * @param view the view to start with - * @param location the container of result + * @param view the view to start with + * @param location the container of result * @param ancestorId the ancestor id */ public static void getCenterInAncestor(View view, int[] location, int ancestorId) { @@ -79,8 +79,8 @@ public final class ViewUtils { /** * Get view location in ths ancestor * - * @param view the view to start with - * @param location the container of result + * @param view the view to start with + * @param location the container of result * @param ancestorId the ancestor id */ public static void getLocationInAncestor(View view, int[] location, int ancestorId) { @@ -117,7 +117,7 @@ public final class ViewUtils { /** * Get view center in ths ancestor * - * @param view the view to start with + * @param view the view to start with * @param location the container of result * @param ancestor the ancestor */ @@ -130,7 +130,7 @@ public final class ViewUtils { /** * Get view location in ths ancestor * - * @param view the view to start with + * @param view the view to start with * @param location the container of result * @param ancestor the ancestor */ @@ -170,7 +170,7 @@ public final class ViewUtils { * id, return this view. * * @param view the view to start with - * @param id The id to search for. + * @param id The id to search for. * @return The view that has the given id in the hierarchy or null */ public static View getAncestor(View view, int id) { @@ -194,7 +194,7 @@ public final class ViewUtils { * id, return this view. * * @param view the view to start with - * @param id the id to search for + * @param id the id to search for * @return the view that has the given id in the hierarchy or null */ public static View getChild(View view, int id) { @@ -262,8 +262,7 @@ public final class ViewUtils { * setLayerType(View.LAYER_TYPE_SOFTWARE, null).' * * - * @param v - * The view + * @param v The view */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static void removeHardwareAccelerationSupport(View v) { @@ -307,8 +306,8 @@ public final class ViewUtils { * parent view's coordinate system. * * @param view Child view of the parent to hit test - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system * @param slop the slop out of the view, or negative for inside * @return true if the supplied view is under the given point, false otherwise */ @@ -334,7 +333,7 @@ public final class ViewUtils { * MeasureSpec imposed no constraints. Will get suitable if allowed * by the MeasureSpec. * - * @param size Default size for this view + * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */ @@ -360,12 +359,12 @@ public final class ViewUtils { * removeOnGlobalLayoutListener * * @param viewTreeObserver the ViewTreeObserver - * @param l the OnGlobalLayoutListener + * @param l the OnGlobalLayoutListener */ @SuppressWarnings("deprecation") public static void removeOnGlobalLayoutListener(ViewTreeObserver viewTreeObserver, - ViewTreeObserver.OnGlobalLayoutListener l) { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + ViewTreeObserver.OnGlobalLayoutListener l) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { viewTreeObserver.removeGlobalOnLayoutListener(l); } else { viewTreeObserver.removeOnGlobalLayoutListener(l); @@ -379,7 +378,7 @@ public final class ViewUtils { * @return The index */ public static int getIndexInParent(View view) { - ViewParent parent = view.getParent(); + ViewParent parent = view.getParent(); if (parent instanceof ViewGroup) { ViewGroup viewParent = (ViewGroup) parent; int count = viewParent.getChildCount(); @@ -396,9 +395,9 @@ public final class ViewUtils { /** * Transform point from parent to child * - * @param point the point + * @param point the point * @param parent the parent - * @param child the child + * @param child the child */ public static void transformPointToViewLocal(float[] point, View parent, View child) { point[0] += parent.getScrollX() - child.getLeft(); @@ -420,7 +419,9 @@ public final class ViewUtils { } private static void dumpViewHierarchy(View view, PrintWriter writer, String prefix) { - writer.write(prefix);writer.write(view.getClass().getName());writer.write('\n'); + writer.write(prefix); + writer.write(view.getClass().getName()); + writer.write('\n'); if (view instanceof ViewGroup) { String newPrefix = prefix + " "; ViewGroup viewGroup = (ViewGroup) view; @@ -439,7 +440,7 @@ public final class ViewUtils { * @return a generated ID value */ public static int generateViewId() { - for (;;) { + for (; ; ) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index c1cd20de2..7b72f4d1c 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -34,7 +34,8 @@ true false true - @style/Widget.Material3.CompoundButton.MaterialSwitch + @style/Widget.Material3.CompoundButton.MaterialSwitch + @style/Widget.Material3.CompoundButton.MaterialSwitch