Add Spotless
This commit is contained in:
parent
2af13a4e6c
commit
defcd86152
286 changed files with 15604 additions and 13757 deletions
|
@ -19,184 +19,173 @@ import android.content.res.TypedArray;
|
|||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.FontRes;
|
||||
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
public class BaselineGridTextView extends MaterialTextView {
|
||||
|
||||
private final float FOUR_DIP;
|
||||
private final float FOUR_DIP;
|
||||
|
||||
private int extraBottomPadding = 0;
|
||||
private int extraBottomPadding = 0;
|
||||
|
||||
private int extraTopPadding = 0;
|
||||
private int extraTopPadding = 0;
|
||||
|
||||
private @FontRes
|
||||
int fontResId = 0;
|
||||
private @FontRes int fontResId = 0;
|
||||
|
||||
private float lineHeightHint = 0f;
|
||||
private float lineHeightHint = 0f;
|
||||
|
||||
private float lineHeightMultiplierHint = 1f;
|
||||
private float lineHeightMultiplierHint = 1f;
|
||||
|
||||
private boolean maxLinesByHeight = false;
|
||||
private boolean maxLinesByHeight = false;
|
||||
|
||||
public BaselineGridTextView(Context context) {
|
||||
this(context, null);
|
||||
public BaselineGridTextView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public BaselineGridTextView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.textViewStyle);
|
||||
}
|
||||
|
||||
public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
final TypedArray a =
|
||||
context.obtainStyledAttributes(attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0);
|
||||
|
||||
// first check TextAppearance for line height & font attributes
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) {
|
||||
int textAppearanceId =
|
||||
a.getResourceId(
|
||||
R.styleable.BaselineGridTextView_android_textAppearance,
|
||||
android.R.style.TextAppearance);
|
||||
TypedArray ta =
|
||||
context.obtainStyledAttributes(textAppearanceId, R.styleable.BaselineGridTextView);
|
||||
parseTextAttrs(ta);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public BaselineGridTextView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.textViewStyle);
|
||||
// then check view attrs
|
||||
parseTextAttrs(a);
|
||||
maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
|
||||
a.recycle();
|
||||
|
||||
FOUR_DIP =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
|
||||
computeLineHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingBottom() {
|
||||
// include extra padding to make the height a multiple of 4dp
|
||||
return super.getCompoundPaddingBottom() + extraBottomPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingTop() {
|
||||
// include extra padding to place the first line's baseline on the grid
|
||||
return super.getCompoundPaddingTop() + extraTopPadding;
|
||||
}
|
||||
|
||||
public @FontRes int getFontResId() {
|
||||
return fontResId;
|
||||
}
|
||||
|
||||
public float getLineHeightHint() {
|
||||
return lineHeightHint;
|
||||
}
|
||||
|
||||
public void setLineHeightHint(float lineHeightHint) {
|
||||
this.lineHeightHint = lineHeightHint;
|
||||
computeLineHeight();
|
||||
}
|
||||
|
||||
public float getLineHeightMultiplierHint() {
|
||||
return lineHeightMultiplierHint;
|
||||
}
|
||||
|
||||
public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
|
||||
this.lineHeightMultiplierHint = lineHeightMultiplierHint;
|
||||
computeLineHeight();
|
||||
}
|
||||
|
||||
public boolean getMaxLinesByHeight() {
|
||||
return maxLinesByHeight;
|
||||
}
|
||||
|
||||
public void setMaxLinesByHeight(boolean maxLinesByHeight) {
|
||||
this.maxLinesByHeight = maxLinesByHeight;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
extraTopPadding = 0;
|
||||
extraBottomPadding = 0;
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int height = getMeasuredHeight();
|
||||
height += ensureBaselineOnGrid();
|
||||
height += ensureHeightGridAligned(height);
|
||||
setMeasuredDimension(getMeasuredWidth(), height);
|
||||
checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
|
||||
}
|
||||
|
||||
/**
|
||||
* When measured with an exact height, text can be vertically clipped mid-line. Prevent this by
|
||||
* setting the {@code maxLines} property based on the available space.
|
||||
*/
|
||||
private void checkMaxLines(int height, int heightMode) {
|
||||
if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) {
|
||||
return;
|
||||
}
|
||||
|
||||
public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
|
||||
int completeLines = (int) Math.floor(textHeight / getLineHeight());
|
||||
setMaxLines(completeLines);
|
||||
}
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0);
|
||||
/** Ensures line height is a multiple of 4dp. */
|
||||
private void computeLineHeight() {
|
||||
final Paint.FontMetrics fm = getPaint().getFontMetrics();
|
||||
final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
|
||||
final float desiredLineHeight =
|
||||
(lineHeightHint > 0) ? lineHeightHint : lineHeightMultiplierHint * fontHeight;
|
||||
|
||||
// first check TextAppearance for line height & font attributes
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) {
|
||||
int textAppearanceId =
|
||||
a.getResourceId(R.styleable.BaselineGridTextView_android_textAppearance,
|
||||
android.R.style.TextAppearance);
|
||||
TypedArray ta = context.obtainStyledAttributes(
|
||||
textAppearanceId, R.styleable.BaselineGridTextView);
|
||||
parseTextAttrs(ta);
|
||||
ta.recycle();
|
||||
}
|
||||
final int baselineAlignedLineHeight =
|
||||
(int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f);
|
||||
setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
|
||||
}
|
||||
|
||||
// then check view attrs
|
||||
parseTextAttrs(a);
|
||||
maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
|
||||
a.recycle();
|
||||
|
||||
FOUR_DIP = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
|
||||
computeLineHeight();
|
||||
/** Ensure that the first line of text sits on the 4dp grid. */
|
||||
private int ensureBaselineOnGrid() {
|
||||
float baseline = getBaseline();
|
||||
float gridAlign = baseline % FOUR_DIP;
|
||||
if (gridAlign != 0) {
|
||||
extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
|
||||
}
|
||||
return extraTopPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingBottom() {
|
||||
// include extra padding to make the height a multiple of 4dp
|
||||
return super.getCompoundPaddingBottom() + extraBottomPadding;
|
||||
/** Ensure that height is a multiple of 4dp. */
|
||||
private int ensureHeightGridAligned(int height) {
|
||||
float gridOverhang = height % FOUR_DIP;
|
||||
if (gridOverhang != 0) {
|
||||
extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
|
||||
}
|
||||
return extraBottomPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingTop() {
|
||||
// include extra padding to place the first line's baseline on the grid
|
||||
return super.getCompoundPaddingTop() + extraTopPadding;
|
||||
private void parseTextAttrs(TypedArray a) {
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) {
|
||||
lineHeightMultiplierHint =
|
||||
a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
|
||||
}
|
||||
|
||||
public @FontRes
|
||||
int getFontResId() {
|
||||
return fontResId;
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) {
|
||||
lineHeightHint = a.getDimensionPixelSize(R.styleable.BaselineGridTextView_lineHeightHint, 0);
|
||||
}
|
||||
|
||||
public float getLineHeightHint() {
|
||||
return lineHeightHint;
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) {
|
||||
fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0);
|
||||
}
|
||||
|
||||
public void setLineHeightHint(float lineHeightHint) {
|
||||
this.lineHeightHint = lineHeightHint;
|
||||
computeLineHeight();
|
||||
}
|
||||
|
||||
public float getLineHeightMultiplierHint() {
|
||||
return lineHeightMultiplierHint;
|
||||
}
|
||||
|
||||
public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
|
||||
this.lineHeightMultiplierHint = lineHeightMultiplierHint;
|
||||
computeLineHeight();
|
||||
}
|
||||
|
||||
public boolean getMaxLinesByHeight() {
|
||||
return maxLinesByHeight;
|
||||
}
|
||||
|
||||
public void setMaxLinesByHeight(boolean maxLinesByHeight) {
|
||||
this.maxLinesByHeight = maxLinesByHeight;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
extraTopPadding = 0;
|
||||
extraBottomPadding = 0;
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int height = getMeasuredHeight();
|
||||
height += ensureBaselineOnGrid();
|
||||
height += ensureHeightGridAligned(height);
|
||||
setMeasuredDimension(getMeasuredWidth(), height);
|
||||
checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
|
||||
}
|
||||
|
||||
/**
|
||||
* When measured with an exact height, text can be vertically clipped mid-line. Prevent
|
||||
* this by setting the {@code maxLines} property based on the available space.
|
||||
*/
|
||||
private void checkMaxLines(int height, int heightMode) {
|
||||
if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) {
|
||||
return;
|
||||
}
|
||||
|
||||
int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
|
||||
int completeLines = (int) Math.floor(textHeight / getLineHeight());
|
||||
setMaxLines(completeLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures line height is a multiple of 4dp.
|
||||
*/
|
||||
private void computeLineHeight() {
|
||||
final Paint.FontMetrics fm = getPaint().getFontMetrics();
|
||||
final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
|
||||
final float desiredLineHeight = (lineHeightHint > 0)
|
||||
? lineHeightHint
|
||||
: lineHeightMultiplierHint * fontHeight;
|
||||
|
||||
final int baselineAlignedLineHeight =
|
||||
(int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f);
|
||||
setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the first line of text sits on the 4dp grid.
|
||||
*/
|
||||
private int ensureBaselineOnGrid() {
|
||||
float baseline = getBaseline();
|
||||
float gridAlign = baseline % FOUR_DIP;
|
||||
if (gridAlign != 0) {
|
||||
extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
|
||||
}
|
||||
return extraTopPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that height is a multiple of 4dp.
|
||||
*/
|
||||
private int ensureHeightGridAligned(int height) {
|
||||
float gridOverhang = height % FOUR_DIP;
|
||||
if (gridOverhang != 0) {
|
||||
extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
|
||||
}
|
||||
return extraBottomPadding;
|
||||
}
|
||||
|
||||
private void parseTextAttrs(TypedArray a) {
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) {
|
||||
lineHeightMultiplierHint =
|
||||
a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
|
||||
}
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) {
|
||||
lineHeightHint = a.getDimensionPixelSize(
|
||||
R.styleable.BaselineGridTextView_lineHeightHint, 0);
|
||||
}
|
||||
if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) {
|
||||
fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,422 +26,429 @@ import android.widget.HorizontalScrollView;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
/** @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) */
|
||||
public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener {
|
||||
|
||||
@ColorInt
|
||||
private int contentColorActivated;
|
||||
@ColorInt
|
||||
private int contentColorDeactivated;
|
||||
private int mActive;
|
||||
private SelectionCallback mCallback;
|
||||
private LinearLayout mChildFrame;
|
||||
// Stores currently visible crumbs
|
||||
private List<Crumb> mCrumbs;
|
||||
// Stores user's navigation history, like a fragment back stack
|
||||
private List<Crumb> mHistory;
|
||||
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
|
||||
private List<Crumb> mOldCrumbs;
|
||||
@ColorInt private int contentColorActivated;
|
||||
@ColorInt private int contentColorDeactivated;
|
||||
private int mActive;
|
||||
private SelectionCallback mCallback;
|
||||
private LinearLayout mChildFrame;
|
||||
// Stores currently visible crumbs
|
||||
private List<Crumb> mCrumbs;
|
||||
// Stores user's navigation history, like a fragment back stack
|
||||
private List<Crumb> mHistory;
|
||||
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
|
||||
private List<Crumb> mOldCrumbs;
|
||||
|
||||
public BreadCrumbLayout(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
public BreadCrumbLayout(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BreadCrumbLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
|
||||
LinearLayout view =
|
||||
(LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false);
|
||||
view.setTag(mCrumbs.size());
|
||||
view.setOnClickListener(this);
|
||||
|
||||
ImageView iv = (ImageView) view.getChildAt(1);
|
||||
if (iv.getDrawable() != null) {
|
||||
iv.getDrawable().setAutoMirrored(true);
|
||||
}
|
||||
iv.setVisibility(View.GONE);
|
||||
|
||||
public BreadCrumbLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
mChildFrame.addView(
|
||||
view,
|
||||
new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
mCrumbs.add(crumb);
|
||||
if (refreshLayout) {
|
||||
mActive = mCrumbs.size() - 1;
|
||||
requestLayout();
|
||||
}
|
||||
invalidateActivatedAll();
|
||||
}
|
||||
|
||||
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
public void addHistory(Crumb crumb) {
|
||||
mHistory.add(crumb);
|
||||
}
|
||||
|
||||
public void clearCrumbs() {
|
||||
try {
|
||||
mOldCrumbs = new ArrayList<>(mCrumbs);
|
||||
mCrumbs.clear();
|
||||
mChildFrame.removeAllViews();
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
|
||||
LinearLayout view = (LinearLayout) LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.bread_crumb, this, false);
|
||||
view.setTag(mCrumbs.size());
|
||||
view.setOnClickListener(this);
|
||||
public void clearHistory() {
|
||||
mHistory.clear();
|
||||
}
|
||||
|
||||
ImageView iv = (ImageView) view.getChildAt(1);
|
||||
if (iv.getDrawable() != null) {
|
||||
iv.getDrawable().setAutoMirrored(true);
|
||||
}
|
||||
iv.setVisibility(View.GONE);
|
||||
|
||||
mChildFrame.addView(view, new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
mCrumbs.add(crumb);
|
||||
if (refreshLayout) {
|
||||
mActive = mCrumbs.size() - 1;
|
||||
requestLayout();
|
||||
}
|
||||
invalidateActivatedAll();
|
||||
public Crumb findCrumb(@NonNull File forDir) {
|
||||
for (int i = 0; i < mCrumbs.size(); i++) {
|
||||
if (mCrumbs.get(i).getFile().equals(forDir)) {
|
||||
return mCrumbs.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addHistory(Crumb crumb) {
|
||||
mHistory.add(crumb);
|
||||
public int getActiveIndex() {
|
||||
return mActive;
|
||||
}
|
||||
|
||||
public Crumb getCrumb(int index) {
|
||||
return mCrumbs.get(index);
|
||||
}
|
||||
|
||||
public SavedStateWrapper getStateWrapper() {
|
||||
return new SavedStateWrapper(this);
|
||||
}
|
||||
|
||||
public int historySize() {
|
||||
return mHistory.size();
|
||||
}
|
||||
|
||||
public Crumb lastHistory() {
|
||||
if (mHistory.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return mHistory.get(mHistory.size() - 1);
|
||||
}
|
||||
|
||||
public void clearCrumbs() {
|
||||
try {
|
||||
mOldCrumbs = new ArrayList<>(mCrumbs);
|
||||
mCrumbs.clear();
|
||||
mChildFrame.removeAllViews();
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCallback != null) {
|
||||
int index = (Integer) v.getTag();
|
||||
mCallback.onCrumbSelection(mCrumbs.get(index), index);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
mHistory.clear();
|
||||
public boolean popHistory() {
|
||||
if (mHistory.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
mHistory.remove(mHistory.size() - 1);
|
||||
return mHistory.size() != 0;
|
||||
}
|
||||
|
||||
public Crumb findCrumb(@NonNull File forDir) {
|
||||
for (int i = 0; i < mCrumbs.size(); i++) {
|
||||
if (mCrumbs.get(i).getFile().equals(forDir)) {
|
||||
return mCrumbs.get(i);
|
||||
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
|
||||
if (mSavedState != null) {
|
||||
mActive = mSavedState.mActive;
|
||||
for (Crumb c : mSavedState.mCrumbs) {
|
||||
addCrumb(c, false);
|
||||
}
|
||||
requestLayout();
|
||||
setVisibility(mSavedState.mVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
public void reverseHistory() {
|
||||
Collections.reverse(mHistory);
|
||||
}
|
||||
|
||||
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
|
||||
this.contentColorActivated = contentColorActivated;
|
||||
}
|
||||
|
||||
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
|
||||
if (forceRecreate || !setActive(crumb)) {
|
||||
clearCrumbs();
|
||||
final List<File> newPathSet = new ArrayList<>();
|
||||
|
||||
newPathSet.add(0, crumb.getFile());
|
||||
|
||||
File p = crumb.getFile();
|
||||
while ((p = p.getParentFile()) != null) {
|
||||
newPathSet.add(0, p);
|
||||
}
|
||||
|
||||
for (int index = 0; index < newPathSet.size(); index++) {
|
||||
final File fi = newPathSet.get(index);
|
||||
crumb = new Crumb(fi);
|
||||
|
||||
// Restore scroll positions saved before clearing
|
||||
if (mOldCrumbs != null) {
|
||||
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
|
||||
Crumb old = iterator.next();
|
||||
if (old.equals(crumb)) {
|
||||
crumb.setScrollPosition(old.getScrollPosition());
|
||||
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from
|
||||
// history
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
addCrumb(crumb, true);
|
||||
}
|
||||
|
||||
// History no longer needed
|
||||
mOldCrumbs = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCallback(SelectionCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
|
||||
this.contentColorDeactivated = contentColorDeactivated;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mCrumbs.size();
|
||||
}
|
||||
|
||||
public boolean trim(String path, boolean dir) {
|
||||
if (!dir) {
|
||||
return false;
|
||||
}
|
||||
int index = -1;
|
||||
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
|
||||
File fi = mCrumbs.get(i).getFile();
|
||||
if (fi.getPath().equals(path)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveIndex() {
|
||||
return mActive;
|
||||
boolean removedActive = index >= mActive;
|
||||
if (index > -1) {
|
||||
while (index <= mCrumbs.size() - 1) {
|
||||
removeCrumbAt(index);
|
||||
}
|
||||
if (mChildFrame.getChildCount() > 0) {
|
||||
int lastIndex = mCrumbs.size() - 1;
|
||||
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
|
||||
}
|
||||
}
|
||||
return removedActive || mCrumbs.size() == 0;
|
||||
}
|
||||
|
||||
public boolean trim(File file) {
|
||||
return trim(file.getPath(), file.isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
// RTL works fine like this
|
||||
View child = mChildFrame.getChildAt(mActive);
|
||||
if (child != null) {
|
||||
smoothScrollTo(child.getLeft(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void invalidateActivatedAll() {
|
||||
for (int i = 0; i < mCrumbs.size(); i++) {
|
||||
Crumb crumb = mCrumbs.get(i);
|
||||
invalidateActivated(
|
||||
mChildFrame.getChildAt(i),
|
||||
mActive == mCrumbs.indexOf(crumb),
|
||||
false,
|
||||
i < mCrumbs.size() - 1)
|
||||
.setText(crumb.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
void removeCrumbAt(int index) {
|
||||
mCrumbs.remove(index);
|
||||
mChildFrame.removeViewAt(index);
|
||||
}
|
||||
|
||||
void updateIndices() {
|
||||
for (int i = 0; i < mChildFrame.getChildCount(); i++) {
|
||||
mChildFrame.getChildAt(i).setTag(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void init() {
|
||||
contentColorActivated =
|
||||
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary);
|
||||
contentColorDeactivated =
|
||||
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary);
|
||||
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
|
||||
setClipToPadding(false);
|
||||
setHorizontalScrollBarEnabled(false);
|
||||
mCrumbs = new ArrayList<>();
|
||||
mHistory = new ArrayList<>();
|
||||
mChildFrame = new LinearLayout(getContext());
|
||||
addView(
|
||||
mChildFrame,
|
||||
new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
private TextView invalidateActivated(
|
||||
View view,
|
||||
final boolean isActive,
|
||||
final boolean noArrowIfAlone,
|
||||
final boolean allowArrowVisible) {
|
||||
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
|
||||
LinearLayout child = (LinearLayout) view;
|
||||
TextView tv = (TextView) child.getChildAt(0);
|
||||
tv.setTextColor(contentColor);
|
||||
ImageView iv = (ImageView) child.getChildAt(1);
|
||||
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
|
||||
if (noArrowIfAlone && getChildCount() == 1) {
|
||||
iv.setVisibility(View.GONE);
|
||||
} else if (allowArrowVisible) {
|
||||
iv.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
iv.setVisibility(View.GONE);
|
||||
}
|
||||
return tv;
|
||||
}
|
||||
|
||||
private boolean setActive(Crumb newActive) {
|
||||
mActive = mCrumbs.indexOf(newActive);
|
||||
invalidateActivatedAll();
|
||||
boolean success = mActive > -1;
|
||||
if (success) {
|
||||
requestLayout();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public interface SelectionCallback {
|
||||
|
||||
void onCrumbSelection(Crumb crumb, int index);
|
||||
}
|
||||
|
||||
public static class Crumb implements Parcelable {
|
||||
|
||||
public static final Creator<Crumb> CREATOR =
|
||||
new Creator<Crumb>() {
|
||||
@Override
|
||||
public Crumb createFromParcel(Parcel source) {
|
||||
return new Crumb(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Crumb[] newArray(int size) {
|
||||
return new Crumb[size];
|
||||
}
|
||||
};
|
||||
|
||||
private final File file;
|
||||
|
||||
private int scrollPos;
|
||||
|
||||
public Crumb(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public Crumb getCrumb(int index) {
|
||||
return mCrumbs.get(index);
|
||||
}
|
||||
|
||||
public SavedStateWrapper getStateWrapper() {
|
||||
return new SavedStateWrapper(this);
|
||||
}
|
||||
|
||||
public int historySize() {
|
||||
return mHistory.size();
|
||||
}
|
||||
|
||||
public Crumb lastHistory() {
|
||||
if (mHistory.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return mHistory.get(mHistory.size() - 1);
|
||||
protected Crumb(Parcel in) {
|
||||
this.file = (File) in.readSerializable();
|
||||
this.scrollPos = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCallback != null) {
|
||||
int index = (Integer) v.getTag();
|
||||
mCallback.onCrumbSelection(mCrumbs.get(index), index);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean popHistory() {
|
||||
if (mHistory.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
mHistory.remove(mHistory.size() - 1);
|
||||
return mHistory.size() != 0;
|
||||
}
|
||||
|
||||
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
|
||||
if (mSavedState != null) {
|
||||
mActive = mSavedState.mActive;
|
||||
for (Crumb c : mSavedState.mCrumbs) {
|
||||
addCrumb(c, false);
|
||||
}
|
||||
requestLayout();
|
||||
setVisibility(mSavedState.mVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
public void reverseHistory() {
|
||||
Collections.reverse(mHistory);
|
||||
}
|
||||
|
||||
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
|
||||
this.contentColorActivated = contentColorActivated;
|
||||
}
|
||||
|
||||
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
|
||||
if (forceRecreate || !setActive(crumb)) {
|
||||
clearCrumbs();
|
||||
final List<File> newPathSet = new ArrayList<>();
|
||||
|
||||
newPathSet.add(0, crumb.getFile());
|
||||
|
||||
File p = crumb.getFile();
|
||||
while ((p = p.getParentFile()) != null) {
|
||||
newPathSet.add(0, p);
|
||||
}
|
||||
|
||||
for (int index = 0; index < newPathSet.size(); index++) {
|
||||
final File fi = newPathSet.get(index);
|
||||
crumb = new Crumb(fi);
|
||||
|
||||
// Restore scroll positions saved before clearing
|
||||
if (mOldCrumbs != null) {
|
||||
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
|
||||
Crumb old = iterator.next();
|
||||
if (old.equals(crumb)) {
|
||||
crumb.setScrollPosition(old.getScrollPosition());
|
||||
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addCrumb(crumb, true);
|
||||
}
|
||||
|
||||
// History no longer needed
|
||||
mOldCrumbs = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCallback(SelectionCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
|
||||
this.contentColorDeactivated = contentColorDeactivated;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mCrumbs.size();
|
||||
}
|
||||
|
||||
public boolean trim(String path, boolean dir) {
|
||||
if (!dir) {
|
||||
return false;
|
||||
}
|
||||
int index = -1;
|
||||
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
|
||||
File fi = mCrumbs.get(i).getFile();
|
||||
if (fi.getPath().equals(path)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean removedActive = index >= mActive;
|
||||
if (index > -1) {
|
||||
while (index <= mCrumbs.size() - 1) {
|
||||
removeCrumbAt(index);
|
||||
}
|
||||
if (mChildFrame.getChildCount() > 0) {
|
||||
int lastIndex = mCrumbs.size() - 1;
|
||||
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
|
||||
}
|
||||
}
|
||||
return removedActive || mCrumbs.size() == 0;
|
||||
}
|
||||
|
||||
public boolean trim(File file) {
|
||||
return trim(file.getPath(), file.isDirectory());
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
//RTL works fine like this
|
||||
View child = mChildFrame.getChildAt(mActive);
|
||||
if (child != null) {
|
||||
smoothScrollTo(child.getLeft(), 0);
|
||||
}
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof Crumb)
|
||||
&& ((Crumb) o).getFile() != null
|
||||
&& ((Crumb) o).getFile().equals(getFile());
|
||||
}
|
||||
|
||||
void invalidateActivatedAll() {
|
||||
for (int i = 0; i < mCrumbs.size(); i++) {
|
||||
Crumb crumb = mCrumbs.get(i);
|
||||
invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false,
|
||||
i < mCrumbs.size() - 1).setText(crumb.getTitle());
|
||||
}
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
void removeCrumbAt(int index) {
|
||||
mCrumbs.remove(index);
|
||||
mChildFrame.removeViewAt(index);
|
||||
public int getScrollPosition() {
|
||||
return scrollPos;
|
||||
}
|
||||
|
||||
void updateIndices() {
|
||||
for (int i = 0; i < mChildFrame.getChildCount(); i++) {
|
||||
mChildFrame.getChildAt(i).setTag(i);
|
||||
}
|
||||
public void setScrollPosition(int scrollY) {
|
||||
this.scrollPos = scrollY;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
contentColorActivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary);
|
||||
contentColorDeactivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary);
|
||||
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
|
||||
setClipToPadding(false);
|
||||
setHorizontalScrollBarEnabled(false);
|
||||
mCrumbs = new ArrayList<>();
|
||||
mHistory = new ArrayList<>();
|
||||
mChildFrame = new LinearLayout(getContext());
|
||||
addView(mChildFrame, new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
public String getTitle() {
|
||||
return file.getPath().equals("/") ? "root" : file.getName();
|
||||
}
|
||||
|
||||
private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone,
|
||||
final boolean allowArrowVisible) {
|
||||
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
|
||||
LinearLayout child = (LinearLayout) view;
|
||||
TextView tv = (TextView) child.getChildAt(0);
|
||||
tv.setTextColor(contentColor);
|
||||
ImageView iv = (ImageView) child.getChildAt(1);
|
||||
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
|
||||
if (noArrowIfAlone && getChildCount() == 1) {
|
||||
iv.setVisibility(View.GONE);
|
||||
} else if (allowArrowVisible) {
|
||||
iv.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
iv.setVisibility(View.GONE);
|
||||
}
|
||||
return tv;
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Crumb{" + "file=" + file + ", scrollPos=" + scrollPos + '}';
|
||||
}
|
||||
|
||||
private boolean setActive(Crumb newActive) {
|
||||
mActive = mCrumbs.indexOf(newActive);
|
||||
invalidateActivatedAll();
|
||||
boolean success = mActive > -1;
|
||||
if (success) {
|
||||
requestLayout();
|
||||
}
|
||||
return success;
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(this.file);
|
||||
dest.writeInt(this.scrollPos);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SelectionCallback {
|
||||
public static class SavedStateWrapper implements Parcelable {
|
||||
|
||||
void onCrumbSelection(Crumb crumb, int index);
|
||||
}
|
||||
public static final Creator<SavedStateWrapper> CREATOR =
|
||||
new Creator<SavedStateWrapper>() {
|
||||
public SavedStateWrapper createFromParcel(Parcel source) {
|
||||
return new SavedStateWrapper(source);
|
||||
}
|
||||
|
||||
public static class Crumb implements Parcelable {
|
||||
|
||||
public static final Creator<Crumb> CREATOR = new Creator<Crumb>() {
|
||||
@Override
|
||||
public Crumb createFromParcel(Parcel source) {
|
||||
return new Crumb(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Crumb[] newArray(int size) {
|
||||
return new Crumb[size];
|
||||
}
|
||||
public SavedStateWrapper[] newArray(int size) {
|
||||
return new SavedStateWrapper[size];
|
||||
}
|
||||
};
|
||||
|
||||
private final File file;
|
||||
public final int mActive;
|
||||
|
||||
private int scrollPos;
|
||||
public final List<Crumb> mCrumbs;
|
||||
|
||||
public Crumb(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
public final int mVisibility;
|
||||
|
||||
protected Crumb(Parcel in) {
|
||||
this.file = (File) in.readSerializable();
|
||||
this.scrollPos = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof Crumb) && ((Crumb) o).getFile() != null &&
|
||||
((Crumb) o).getFile().equals(getFile());
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public int getScrollPosition() {
|
||||
return scrollPos;
|
||||
}
|
||||
|
||||
public void setScrollPosition(int scrollY) {
|
||||
this.scrollPos = scrollY;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return file.getPath().equals("/") ? "root" : file.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Crumb{" +
|
||||
"file=" + file +
|
||||
", scrollPos=" + scrollPos +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(this.file);
|
||||
dest.writeInt(this.scrollPos);
|
||||
}
|
||||
public SavedStateWrapper(BreadCrumbLayout view) {
|
||||
mActive = view.mActive;
|
||||
mCrumbs = view.mCrumbs;
|
||||
mVisibility = view.getVisibility();
|
||||
}
|
||||
|
||||
public static class SavedStateWrapper implements Parcelable {
|
||||
|
||||
public static final Creator<SavedStateWrapper> CREATOR = new Creator<SavedStateWrapper>() {
|
||||
public SavedStateWrapper createFromParcel(Parcel source) {
|
||||
return new SavedStateWrapper(source);
|
||||
}
|
||||
|
||||
public SavedStateWrapper[] newArray(int size) {
|
||||
return new SavedStateWrapper[size];
|
||||
}
|
||||
};
|
||||
|
||||
public final int mActive;
|
||||
|
||||
public final List<Crumb> mCrumbs;
|
||||
|
||||
public final int mVisibility;
|
||||
|
||||
public SavedStateWrapper(BreadCrumbLayout view) {
|
||||
mActive = view.mActive;
|
||||
mCrumbs = view.mCrumbs;
|
||||
mVisibility = view.getVisibility();
|
||||
}
|
||||
|
||||
protected SavedStateWrapper(Parcel in) {
|
||||
this.mActive = in.readInt();
|
||||
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
|
||||
this.mVisibility = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mActive);
|
||||
dest.writeTypedList(mCrumbs);
|
||||
dest.writeInt(this.mVisibility);
|
||||
}
|
||||
protected SavedStateWrapper(Parcel in) {
|
||||
this.mActive = in.readInt();
|
||||
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
|
||||
this.mVisibility = in.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mActive);
|
||||
dest.writeTypedList(mCrumbs);
|
||||
dest.writeInt(this.mVisibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,299 +27,311 @@ import android.graphics.drawable.BitmapDrawable;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class CircularImageView extends AppCompatImageView {
|
||||
|
||||
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
|
||||
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
|
||||
|
||||
// Default Values
|
||||
private static final float DEFAULT_BORDER_WIDTH = 4;
|
||||
private static final float DEFAULT_SHADOW_RADIUS = 8.0f;
|
||||
// Default Values
|
||||
private static final float DEFAULT_BORDER_WIDTH = 4;
|
||||
private static final float DEFAULT_SHADOW_RADIUS = 8.0f;
|
||||
|
||||
// Properties
|
||||
private float borderWidth;
|
||||
private int canvasSize;
|
||||
private float shadowRadius;
|
||||
private int shadowColor = Color.BLACK;
|
||||
// Properties
|
||||
private float borderWidth;
|
||||
private int canvasSize;
|
||||
private float shadowRadius;
|
||||
private int shadowColor = Color.BLACK;
|
||||
|
||||
// Object used to draw
|
||||
private Bitmap image;
|
||||
private Drawable drawable;
|
||||
private Paint paint;
|
||||
private Paint paintBorder;
|
||||
// Object used to draw
|
||||
private Bitmap image;
|
||||
private Drawable drawable;
|
||||
private Paint paint;
|
||||
private Paint paintBorder;
|
||||
|
||||
//region Constructor & Init Method
|
||||
public CircularImageView(final Context context) {
|
||||
this(context, null);
|
||||
// region Constructor & Init Method
|
||||
public CircularImageView(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
// Init paint
|
||||
paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
|
||||
paintBorder = new Paint();
|
||||
paintBorder.setAntiAlias(true);
|
||||
|
||||
// Load the styled attributes and set their properties
|
||||
TypedArray attributes =
|
||||
context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0);
|
||||
|
||||
// Init Border
|
||||
if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) {
|
||||
float defaultBorderSize =
|
||||
DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density;
|
||||
setBorderWidth(
|
||||
attributes.getDimension(
|
||||
R.styleable.CircularImageView_civ_border_width, defaultBorderSize));
|
||||
setBorderColor(
|
||||
attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE));
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
// Init Shadow
|
||||
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) {
|
||||
shadowRadius = DEFAULT_SHADOW_RADIUS;
|
||||
drawShadow(
|
||||
attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius),
|
||||
attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor));
|
||||
}
|
||||
attributes.recycle();
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Set Attr Method
|
||||
public void setBorderWidth(float borderWidth) {
|
||||
this.borderWidth = borderWidth;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBorderColor(int borderColor) {
|
||||
if (paintBorder != null) {
|
||||
paintBorder.setColor(borderColor);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void addShadow() {
|
||||
if (shadowRadius == 0) {
|
||||
shadowRadius = DEFAULT_SHADOW_RADIUS;
|
||||
}
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setShadowRadius(float shadowRadius) {
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setShadowColor(int shadowColor) {
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleType getScaleType() {
|
||||
return SCALE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleType(ScaleType scaleType) {
|
||||
if (scaleType != SCALE_TYPE) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.",
|
||||
scaleType));
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Draw Method
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
// Load the bitmap
|
||||
loadBitmap();
|
||||
|
||||
// Check if image isn't null
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
if (!isInEditMode()) {
|
||||
canvasSize = canvas.getWidth();
|
||||
if (canvas.getHeight() < canvasSize) {
|
||||
canvasSize = canvas.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
// Init paint
|
||||
paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
// circleCenter is the x or y of the view's center
|
||||
// radius is the radius in pixels of the cirle to be drawn
|
||||
// paint contains the shader that will texture the shape
|
||||
int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2;
|
||||
// Draw Border
|
||||
canvas.drawCircle(
|
||||
circleCenter + borderWidth,
|
||||
circleCenter + borderWidth,
|
||||
circleCenter + borderWidth - (shadowRadius + shadowRadius / 2),
|
||||
paintBorder);
|
||||
// Draw CircularImageView
|
||||
canvas.drawCircle(
|
||||
circleCenter + borderWidth,
|
||||
circleCenter + borderWidth,
|
||||
circleCenter - (shadowRadius + shadowRadius / 2),
|
||||
paint);
|
||||
}
|
||||
|
||||
paintBorder = new Paint();
|
||||
paintBorder.setAntiAlias(true);
|
||||
|
||||
// Load the styled attributes and set their properties
|
||||
TypedArray attributes = context
|
||||
.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0);
|
||||
|
||||
// Init Border
|
||||
if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) {
|
||||
float defaultBorderSize =
|
||||
DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density;
|
||||
setBorderWidth(attributes
|
||||
.getDimension(R.styleable.CircularImageView_civ_border_width, defaultBorderSize));
|
||||
setBorderColor(
|
||||
attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE));
|
||||
}
|
||||
|
||||
// Init Shadow
|
||||
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) {
|
||||
shadowRadius = DEFAULT_SHADOW_RADIUS;
|
||||
drawShadow(attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius),
|
||||
attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor));
|
||||
}
|
||||
attributes.recycle();
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Set Attr Method
|
||||
public void setBorderWidth(float borderWidth) {
|
||||
this.borderWidth = borderWidth;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
private void loadBitmap() {
|
||||
if (this.drawable == getDrawable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void setBorderColor(int borderColor) {
|
||||
if (paintBorder != null) {
|
||||
paintBorder.setColor(borderColor);
|
||||
}
|
||||
invalidate();
|
||||
this.drawable = getDrawable();
|
||||
this.image = drawableToBitmap(this.drawable);
|
||||
updateShader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
canvasSize = w;
|
||||
if (h < canvasSize) {
|
||||
canvasSize = h;
|
||||
}
|
||||
if (image != null) {
|
||||
updateShader();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawShadow(float shadowRadius, int shadowColor) {
|
||||
this.shadowRadius = shadowRadius;
|
||||
this.shadowColor = shadowColor;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
|
||||
}
|
||||
paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor);
|
||||
}
|
||||
|
||||
private void updateShader() {
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void addShadow() {
|
||||
if (shadowRadius == 0) {
|
||||
shadowRadius = DEFAULT_SHADOW_RADIUS;
|
||||
}
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
// Crop Center Image
|
||||
image = cropBitmap(image);
|
||||
|
||||
// Create Shader
|
||||
BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
|
||||
// Center Image in Shader
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setScale(
|
||||
(float) canvasSize / (float) image.getWidth(),
|
||||
(float) canvasSize / (float) image.getHeight());
|
||||
shader.setLocalMatrix(matrix);
|
||||
|
||||
// Set Shader in Paint
|
||||
paint.setShader(shader);
|
||||
}
|
||||
|
||||
private Bitmap cropBitmap(Bitmap bitmap) {
|
||||
Bitmap bmp;
|
||||
if (bitmap.getWidth() >= bitmap.getHeight()) {
|
||||
bmp =
|
||||
Bitmap.createBitmap(
|
||||
bitmap,
|
||||
bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
|
||||
0,
|
||||
bitmap.getHeight(),
|
||||
bitmap.getHeight());
|
||||
} else {
|
||||
bmp =
|
||||
Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
|
||||
bitmap.getWidth(),
|
||||
bitmap.getWidth());
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private Bitmap drawableToBitmap(Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
} else if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
public void setShadowRadius(float shadowRadius) {
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
int intrinsicWidth = drawable.getIntrinsicWidth();
|
||||
int intrinsicHeight = drawable.getIntrinsicHeight();
|
||||
|
||||
if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setShadowColor(int shadowColor) {
|
||||
drawShadow(shadowRadius, shadowColor);
|
||||
invalidate();
|
||||
try {
|
||||
// Create Bitmap object out of the drawable
|
||||
Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bitmap;
|
||||
} catch (OutOfMemoryError e) {
|
||||
// Simply return null of failed bitmap creations
|
||||
Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Mesure Method
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int width = measureWidth(widthMeasureSpec);
|
||||
int height = measureHeight(heightMeasureSpec);
|
||||
/*int imageSize = (width < height) ? width : height;
|
||||
setMeasuredDimension(imageSize, imageSize);*/
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
private int measureWidth(int measureSpec) {
|
||||
int result;
|
||||
int specMode = MeasureSpec.getMode(measureSpec);
|
||||
int specSize = MeasureSpec.getSize(measureSpec);
|
||||
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
// The parent has determined an exact size for the child.
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
// The child can be as large as it wants up to the specified size.
|
||||
result = specSize;
|
||||
} else {
|
||||
// The parent has not imposed any constraint on the child.
|
||||
result = canvasSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleType getScaleType() {
|
||||
return SCALE_TYPE;
|
||||
return result;
|
||||
}
|
||||
|
||||
private int measureHeight(int measureSpecHeight) {
|
||||
int result;
|
||||
int specMode = MeasureSpec.getMode(measureSpecHeight);
|
||||
int specSize = MeasureSpec.getSize(measureSpecHeight);
|
||||
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
// We were told how big to be
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
// The child can be as large as it wants up to the specified size.
|
||||
result = specSize;
|
||||
} else {
|
||||
// Measure the text (beware: ascent is a negative number)
|
||||
result = canvasSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleType(ScaleType scaleType) {
|
||||
if (scaleType != SCALE_TYPE) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.",
|
||||
scaleType));
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Draw Method
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
// Load the bitmap
|
||||
loadBitmap();
|
||||
|
||||
// Check if image isn't null
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInEditMode()) {
|
||||
canvasSize = canvas.getWidth();
|
||||
if (canvas.getHeight() < canvasSize) {
|
||||
canvasSize = canvas.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
// circleCenter is the x or y of the view's center
|
||||
// radius is the radius in pixels of the cirle to be drawn
|
||||
// paint contains the shader that will texture the shape
|
||||
int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2;
|
||||
// Draw Border
|
||||
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth,
|
||||
circleCenter + borderWidth - (shadowRadius + shadowRadius / 2), paintBorder);
|
||||
// Draw CircularImageView
|
||||
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth,
|
||||
circleCenter - (shadowRadius + shadowRadius / 2), paint);
|
||||
}
|
||||
|
||||
private void loadBitmap() {
|
||||
if (this.drawable == getDrawable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawable = getDrawable();
|
||||
this.image = drawableToBitmap(this.drawable);
|
||||
updateShader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
canvasSize = w;
|
||||
if (h < canvasSize) {
|
||||
canvasSize = h;
|
||||
}
|
||||
if (image != null) {
|
||||
updateShader();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawShadow(float shadowRadius, int shadowColor) {
|
||||
this.shadowRadius = shadowRadius;
|
||||
this.shadowColor = shadowColor;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
|
||||
}
|
||||
paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor);
|
||||
}
|
||||
|
||||
private void updateShader() {
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Crop Center Image
|
||||
image = cropBitmap(image);
|
||||
|
||||
// Create Shader
|
||||
BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
|
||||
// Center Image in Shader
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setScale((float) canvasSize / (float) image.getWidth(),
|
||||
(float) canvasSize / (float) image.getHeight());
|
||||
shader.setLocalMatrix(matrix);
|
||||
|
||||
// Set Shader in Paint
|
||||
paint.setShader(shader);
|
||||
}
|
||||
|
||||
private Bitmap cropBitmap(Bitmap bitmap) {
|
||||
Bitmap bmp;
|
||||
if (bitmap.getWidth() >= bitmap.getHeight()) {
|
||||
bmp = Bitmap.createBitmap(
|
||||
bitmap,
|
||||
bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
|
||||
0,
|
||||
bitmap.getHeight(), bitmap.getHeight());
|
||||
} else {
|
||||
bmp = Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
|
||||
bitmap.getWidth(), bitmap.getWidth());
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private Bitmap drawableToBitmap(Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
} else if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
int intrinsicWidth = drawable.getIntrinsicWidth();
|
||||
int intrinsicHeight = drawable.getIntrinsicHeight();
|
||||
|
||||
if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create Bitmap object out of the drawable
|
||||
Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bitmap;
|
||||
} catch (OutOfMemoryError e) {
|
||||
// Simply return null of failed bitmap creations
|
||||
Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Mesure Method
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int width = measureWidth(widthMeasureSpec);
|
||||
int height = measureHeight(heightMeasureSpec);
|
||||
/*int imageSize = (width < height) ? width : height;
|
||||
setMeasuredDimension(imageSize, imageSize);*/
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
private int measureWidth(int measureSpec) {
|
||||
int result;
|
||||
int specMode = MeasureSpec.getMode(measureSpec);
|
||||
int specSize = MeasureSpec.getSize(measureSpec);
|
||||
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
// The parent has determined an exact size for the child.
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
// The child can be as large as it wants up to the specified size.
|
||||
result = specSize;
|
||||
} else {
|
||||
// The parent has not imposed any constraint on the child.
|
||||
result = canvasSize;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int measureHeight(int measureSpecHeight) {
|
||||
int result;
|
||||
int specMode = MeasureSpec.getMode(measureSpecHeight);
|
||||
int specSize = MeasureSpec.getSize(measureSpecHeight);
|
||||
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
// We were told how big to be
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
// The child can be as large as it wants up to the specified size.
|
||||
result = specSize;
|
||||
} else {
|
||||
// Measure the text (beware: ascent is a negative number)
|
||||
result = canvasSize;
|
||||
}
|
||||
|
||||
return (result + 2);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
return (result + 2);
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import static code.name.monkey.retromusic.util.RetroUtil.openUrl;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
|
@ -22,55 +24,54 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
import static code.name.monkey.retromusic.util.RetroUtil.openUrl;
|
||||
|
||||
public class ContributorsView extends FrameLayout {
|
||||
public ContributorsView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
final TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0);
|
||||
if (attributes != null) {
|
||||
final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this);
|
||||
|
||||
NetworkImageView networkImageView = layout.findViewById(R.id.image);
|
||||
String url = attributes.getString(R.styleable.ContributorsView_profile_url);
|
||||
networkImageView.setImageUrl(url);
|
||||
|
||||
String name = attributes.getString(R.styleable.ContributorsView_profile_name);
|
||||
TextView title = layout.findViewById(R.id.title);
|
||||
title.setText(name);
|
||||
|
||||
String summary = attributes.getString(R.styleable.ContributorsView_profile_summary);
|
||||
TextView text = layout.findViewById(R.id.text);
|
||||
text.setText(summary);
|
||||
|
||||
String link = attributes.getString(R.styleable.ContributorsView_profile_link);
|
||||
layout.setOnClickListener(v -> {
|
||||
if (link == null) {
|
||||
return;
|
||||
}
|
||||
openUrl((Activity) getContext(), link);
|
||||
});
|
||||
attributes.recycle();
|
||||
}
|
||||
public ContributorsView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public ContributorsView(
|
||||
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
final TypedArray attributes =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0);
|
||||
if (attributes != null) {
|
||||
final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this);
|
||||
|
||||
NetworkImageView networkImageView = layout.findViewById(R.id.image);
|
||||
String url = attributes.getString(R.styleable.ContributorsView_profile_url);
|
||||
networkImageView.setImageUrl(url);
|
||||
|
||||
String name = attributes.getString(R.styleable.ContributorsView_profile_name);
|
||||
TextView title = layout.findViewById(R.id.title);
|
||||
title.setText(name);
|
||||
|
||||
String summary = attributes.getString(R.styleable.ContributorsView_profile_summary);
|
||||
TextView text = layout.findViewById(R.id.text);
|
||||
text.setText(summary);
|
||||
|
||||
String link = attributes.getString(R.styleable.ContributorsView_profile_link);
|
||||
layout.setOnClickListener(
|
||||
v -> {
|
||||
if (link == null) {
|
||||
return;
|
||||
}
|
||||
openUrl((Activity) getContext(), link);
|
||||
});
|
||||
attributes.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,20 +17,19 @@ package code.name.monkey.retromusic.views;
|
|||
import android.graphics.drawable.GradientDrawable;
|
||||
|
||||
public class DrawableGradient extends GradientDrawable {
|
||||
public DrawableGradient(Orientation orientations, int[] colors, int shape) {
|
||||
super(orientations, colors);
|
||||
try {
|
||||
setShape(shape);
|
||||
setGradientType(GradientDrawable.LINEAR_GRADIENT);
|
||||
setCornerRadius(0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public DrawableGradient SetTransparency(int transparencyPercent) {
|
||||
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
|
||||
return this;
|
||||
public DrawableGradient(Orientation orientations, int[] colors, int shape) {
|
||||
super(orientations, colors);
|
||||
try {
|
||||
setShape(shape);
|
||||
setGradientType(GradientDrawable.LINEAR_GRADIENT);
|
||||
setCornerRadius(0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public DrawableGradient SetTransparency(int transparencyPercent) {
|
||||
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,34 +20,34 @@ import android.util.AttributeSet;
|
|||
import android.widget.FrameLayout;
|
||||
|
||||
public class HeightFitSquareLayout extends FrameLayout {
|
||||
private boolean forceSquare = true;
|
||||
private boolean forceSquare = true;
|
||||
|
||||
public HeightFitSquareLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
public HeightFitSquareLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) {
|
||||
super(context, attributeSet, i);
|
||||
}
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) {
|
||||
super(context, attributeSet, i);
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) {
|
||||
super(context, attributeSet, i, i2);
|
||||
}
|
||||
@TargetApi(21)
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) {
|
||||
super(context, attributeSet, i, i2);
|
||||
}
|
||||
|
||||
public void forceSquare(boolean z) {
|
||||
this.forceSquare = z;
|
||||
requestLayout();
|
||||
}
|
||||
public void forceSquare(boolean z) {
|
||||
this.forceSquare = z;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
protected void onMeasure(int i, int i2) {
|
||||
if (this.forceSquare) {
|
||||
i = i2;
|
||||
}
|
||||
super.onMeasure(i, i2);
|
||||
protected void onMeasure(int i, int i2) {
|
||||
if (this.forceSquare) {
|
||||
i = i2;
|
||||
}
|
||||
super.onMeasure(i, i2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,28 +22,30 @@ import android.util.AttributeSet;
|
|||
import android.webkit.WebView;
|
||||
|
||||
public class LollipopFixedWebView extends WebView {
|
||||
public LollipopFixedWebView(Context context) {
|
||||
super(getFixedContext(context));
|
||||
}
|
||||
public LollipopFixedWebView(Context context) {
|
||||
super(getFixedContext(context));
|
||||
}
|
||||
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs) {
|
||||
super(getFixedContext(context), attrs);
|
||||
}
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs) {
|
||||
super(getFixedContext(context), attrs);
|
||||
}
|
||||
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr);
|
||||
}
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LollipopFixedWebView(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
|
||||
}
|
||||
public LollipopFixedWebView(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
|
||||
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
|
||||
}
|
||||
|
||||
public static Context getFixedContext(Context context) {
|
||||
return context.createConfigurationContext(new Configuration());
|
||||
}
|
||||
}
|
||||
public static Context getFixedContext(Context context) {
|
||||
return context.createConfigurationContext(new Configuration());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,50 +17,47 @@ package code.name.monkey.retromusic.views;
|
|||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
*/
|
||||
/** @author Hemanth S (h4h13). */
|
||||
public class NetworkImageView extends CircularImageView {
|
||||
|
||||
public NetworkImageView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
public NetworkImageView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
public NetworkImageView(
|
||||
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public void setImageUrl(@NonNull String imageUrl) {
|
||||
setImageUrl(getContext(), imageUrl);
|
||||
}
|
||||
public void setImageUrl(@NonNull String imageUrl) {
|
||||
setImageUrl(getContext(), imageUrl);
|
||||
}
|
||||
|
||||
public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) {
|
||||
Glide.with(context)
|
||||
.load(imageUrl)
|
||||
.error(R.drawable.ic_account)
|
||||
.placeholder(R.drawable.ic_account)
|
||||
.into(this);
|
||||
}
|
||||
public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) {
|
||||
Glide.with(context)
|
||||
.load(imageUrl)
|
||||
.error(R.drawable.ic_account)
|
||||
.placeholder(R.drawable.ic_account)
|
||||
.into(this);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0);
|
||||
String url = attributes.getString(R.styleable.NetworkImageView_url_link);
|
||||
setImageUrl(context, url);
|
||||
attributes.recycle();
|
||||
}
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
TypedArray attributes =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0);
|
||||
String url = attributes.getString(R.styleable.NetworkImageView_url_link);
|
||||
setImageUrl(context, url);
|
||||
attributes.recycle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,134 +27,135 @@ import android.graphics.Rect;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import code.name.monkey.appthemehelper.ThemeStore;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class PopupBackground extends Drawable {
|
||||
|
||||
private final int mPaddingEnd;
|
||||
private final int mPaddingEnd;
|
||||
|
||||
private final int mPaddingStart;
|
||||
private final int mPaddingStart;
|
||||
|
||||
@NonNull
|
||||
private final Paint mPaint;
|
||||
@NonNull private final Paint mPaint;
|
||||
|
||||
@NonNull
|
||||
private final Path mPath = new Path();
|
||||
@NonNull private final Path mPath = new Path();
|
||||
|
||||
@NonNull
|
||||
private final Matrix mTempMatrix = new Matrix();
|
||||
@NonNull private final Matrix mTempMatrix = new Matrix();
|
||||
|
||||
public PopupBackground(@NonNull Context context) {
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(ThemeStore.Companion.accentColor(context));
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
Resources resources = context.getResources();
|
||||
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
|
||||
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
|
||||
public PopupBackground(@NonNull Context context) {
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(ThemeStore.Companion.accentColor(context));
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
Resources resources = context.getResources();
|
||||
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
|
||||
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
|
||||
}
|
||||
|
||||
private static void pathArcTo(
|
||||
@NonNull Path path,
|
||||
float centerX,
|
||||
float centerY,
|
||||
float radius,
|
||||
float startAngle,
|
||||
float sweepAngle) {
|
||||
path.arcTo(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius,
|
||||
startAngle,
|
||||
sweepAngle,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(@NonNull Outline outline) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
|
||||
// The outline path must be convex before Q, but we may run into floating point error
|
||||
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
|
||||
// case we just omit the shadow instead of crashing.
|
||||
super.getOutline(outline);
|
||||
return;
|
||||
}
|
||||
outline.setConvexPath(mPath);
|
||||
}
|
||||
|
||||
private static void pathArcTo(@NonNull Path path, float centerX, float centerY, float radius,
|
||||
float startAngle, float sweepAngle) {
|
||||
path.arcTo(centerX - radius, centerY - radius, centerX + radius, centerY + radius,
|
||||
startAngle, sweepAngle, false);
|
||||
@Override
|
||||
public void setAlpha(int alpha) {}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(@NonNull Rect padding) {
|
||||
if (needMirroring()) {
|
||||
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
|
||||
} else {
|
||||
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
@Override
|
||||
public boolean isAutoMirrored() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLayoutDirectionChanged(int layoutDirection) {
|
||||
updatePath();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds) {
|
||||
updatePath();
|
||||
}
|
||||
|
||||
private boolean needMirroring() {
|
||||
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
private void updatePath() {
|
||||
|
||||
mPath.reset();
|
||||
|
||||
Rect bounds = getBounds();
|
||||
float width = bounds.width();
|
||||
float height = bounds.height();
|
||||
float r = height / 2;
|
||||
float sqrt2 = (float) Math.sqrt(2);
|
||||
// Ensure we are convex.
|
||||
width = Math.max(r + sqrt2 * r, width);
|
||||
pathArcTo(mPath, r, r, r, 90, 180);
|
||||
float o1X = width - sqrt2 * r;
|
||||
pathArcTo(mPath, o1X, r, r, -90, 45f);
|
||||
float r2 = r / 5;
|
||||
float o2X = width - sqrt2 * r2;
|
||||
pathArcTo(mPath, o2X, r, r2, -45, 90);
|
||||
pathArcTo(mPath, o1X, r, r, 45f, 45f);
|
||||
mPath.close();
|
||||
|
||||
if (needMirroring()) {
|
||||
mTempMatrix.setScale(-1, 1, width / 2, 0);
|
||||
} else {
|
||||
mTempMatrix.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(@NonNull Outline outline) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
|
||||
// The outline path must be convex before Q, but we may run into floating point error
|
||||
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
|
||||
// case we just omit the shadow instead of crashing.
|
||||
super.getOutline(outline);
|
||||
return;
|
||||
}
|
||||
outline.setConvexPath(mPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(@NonNull Rect padding) {
|
||||
if (needMirroring()) {
|
||||
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
|
||||
} else {
|
||||
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoMirrored() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLayoutDirectionChanged(int layoutDirection) {
|
||||
updatePath();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds) {
|
||||
updatePath();
|
||||
}
|
||||
|
||||
private boolean needMirroring() {
|
||||
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
private void updatePath() {
|
||||
|
||||
mPath.reset();
|
||||
|
||||
Rect bounds = getBounds();
|
||||
float width = bounds.width();
|
||||
float height = bounds.height();
|
||||
float r = height / 2;
|
||||
float sqrt2 = (float) Math.sqrt(2);
|
||||
// Ensure we are convex.
|
||||
width = Math.max(r + sqrt2 * r, width);
|
||||
pathArcTo(mPath, r, r, r, 90, 180);
|
||||
float o1X = width - sqrt2 * r;
|
||||
pathArcTo(mPath, o1X, r, r, -90, 45f);
|
||||
float r2 = r / 5;
|
||||
float o2X = width - sqrt2 * r2;
|
||||
pathArcTo(mPath, o2X, r, r2, -45, 90);
|
||||
pathArcTo(mPath, o1X, r, r, 45f, 45f);
|
||||
mPath.close();
|
||||
|
||||
if (needMirroring()) {
|
||||
mTempMatrix.setScale(-1, 1, width / 2, 0);
|
||||
} else {
|
||||
mTempMatrix.reset();
|
||||
}
|
||||
mTempMatrix.postTranslate(bounds.left, bounds.top);
|
||||
mPath.transform(mTempMatrix);
|
||||
}
|
||||
}
|
||||
mTempMatrix.postTranslate(bounds.left, bounds.top);
|
||||
mPath.transform(mTempMatrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,42 +17,46 @@ package code.name.monkey.retromusic.views;
|
|||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import me.zhanghai.android.fastscroll.FastScroller;
|
||||
|
||||
public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
|
||||
|
||||
@NonNull
|
||||
private final Rect mPadding = new Rect();
|
||||
@Nullable
|
||||
private final FastScroller mFastScroller;
|
||||
@NonNull private final Rect mPadding = new Rect();
|
||||
@Nullable private final FastScroller mFastScroller;
|
||||
|
||||
public ScrollingViewOnApplyWindowInsetsListener(@Nullable View view,
|
||||
@Nullable FastScroller fastScroller) {
|
||||
if (view != null) {
|
||||
mPadding.set(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
|
||||
view.getPaddingBottom());
|
||||
}
|
||||
mFastScroller = fastScroller;
|
||||
public ScrollingViewOnApplyWindowInsetsListener(
|
||||
@Nullable View view, @Nullable FastScroller fastScroller) {
|
||||
if (view != null) {
|
||||
mPadding.set(
|
||||
view.getPaddingLeft(),
|
||||
view.getPaddingTop(),
|
||||
view.getPaddingRight(),
|
||||
view.getPaddingBottom());
|
||||
}
|
||||
mFastScroller = fastScroller;
|
||||
}
|
||||
|
||||
public ScrollingViewOnApplyWindowInsetsListener() {
|
||||
this(null, null);
|
||||
}
|
||||
public ScrollingViewOnApplyWindowInsetsListener() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) {
|
||||
view.setPadding(mPadding.left + insets.getSystemWindowInsetLeft(), mPadding.top,
|
||||
mPadding.right + insets.getSystemWindowInsetRight(),
|
||||
mPadding.bottom + insets.getSystemWindowInsetBottom());
|
||||
if (mFastScroller != null) {
|
||||
mFastScroller.setPadding(insets.getSystemWindowInsetLeft(), 0,
|
||||
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
|
||||
}
|
||||
return insets;
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) {
|
||||
view.setPadding(
|
||||
mPadding.left + insets.getSystemWindowInsetLeft(),
|
||||
mPadding.top,
|
||||
mPadding.right + insets.getSystemWindowInsetRight(),
|
||||
mPadding.bottom + insets.getSystemWindowInsetBottom());
|
||||
if (mFastScroller != null) {
|
||||
mFastScroller.setPadding(
|
||||
insets.getSystemWindowInsetLeft(),
|
||||
0,
|
||||
insets.getSystemWindowInsetRight(),
|
||||
insets.getSystemWindowInsetBottom());
|
||||
}
|
||||
}
|
||||
return insets;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,529 +24,488 @@ import android.util.AttributeSet;
|
|||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* SeekArc.java
|
||||
* <p>
|
||||
* This is a class that functions much like a SeekBar but
|
||||
* follows a circle path instead of a straight line.
|
||||
*
|
||||
* <p>This is a class that functions much like a SeekBar but follows a circle path instead of a
|
||||
* straight line.
|
||||
*
|
||||
* @author Neil Davies
|
||||
*/
|
||||
public class SeekArc extends View {
|
||||
|
||||
private static final String TAG = SeekArc.class.getSimpleName();
|
||||
private static int INVALID_PROGRESS_VALUE = -1;
|
||||
// The initial rotational offset -90 means we start at 12 o'clock
|
||||
private final int mAngleOffset = -90;
|
||||
private Paint mArcPaint;
|
||||
// Internal variables
|
||||
private int mArcRadius = 0;
|
||||
private RectF mArcRect = new RectF();
|
||||
/**
|
||||
* The Width of the background arc for the SeekArc
|
||||
*/
|
||||
private int mArcWidth = 2;
|
||||
/**
|
||||
* Will the progress increase clockwise or anti-clockwise
|
||||
*/
|
||||
private boolean mClockwise = true;
|
||||
/**
|
||||
* is the control enabled/touchable
|
||||
*/
|
||||
private boolean mEnabled = true;
|
||||
/**
|
||||
* The Maximum value that this SeekArc can be set to
|
||||
*/
|
||||
private int mMax = 100;
|
||||
private OnSeekArcChangeListener mOnSeekArcChangeListener;
|
||||
/**
|
||||
* The Current value that the SeekArc is set to
|
||||
*/
|
||||
private int mProgress = 0;
|
||||
private Paint mProgressPaint;
|
||||
private float mProgressSweep = 0;
|
||||
/**
|
||||
* The width of the progress line for this SeekArc
|
||||
*/
|
||||
private int mProgressWidth = 4;
|
||||
/**
|
||||
* The rotation of the SeekArc- 0 is twelve o'clock
|
||||
*/
|
||||
private int mRotation = 0;
|
||||
/**
|
||||
* Give the SeekArc rounded edges
|
||||
*/
|
||||
private boolean mRoundedEdges = false;
|
||||
/**
|
||||
* The Angle to start drawing this Arc from
|
||||
*/
|
||||
private int mStartAngle = 0;
|
||||
/**
|
||||
* The Angle through which to draw the arc (Max is 360)
|
||||
*/
|
||||
private int mSweepAngle = 360;
|
||||
/**
|
||||
* The Drawable for the seek arc thumbnail
|
||||
*/
|
||||
private Drawable mThumb;
|
||||
private int mThumbXPos;
|
||||
private int mThumbYPos;
|
||||
private double mTouchAngle;
|
||||
private float mTouchIgnoreRadius;
|
||||
/**
|
||||
* Enable touch inside the SeekArc
|
||||
*/
|
||||
private boolean mTouchInside = true;
|
||||
private int mTranslateX;
|
||||
private int mTranslateY;
|
||||
private static final String TAG = SeekArc.class.getSimpleName();
|
||||
private static int INVALID_PROGRESS_VALUE = -1;
|
||||
// The initial rotational offset -90 means we start at 12 o'clock
|
||||
private final int mAngleOffset = -90;
|
||||
private Paint mArcPaint;
|
||||
// Internal variables
|
||||
private int mArcRadius = 0;
|
||||
private RectF mArcRect = new RectF();
|
||||
/** The Width of the background arc for the SeekArc */
|
||||
private int mArcWidth = 2;
|
||||
/** Will the progress increase clockwise or anti-clockwise */
|
||||
private boolean mClockwise = true;
|
||||
/** is the control enabled/touchable */
|
||||
private boolean mEnabled = true;
|
||||
/** The Maximum value that this SeekArc can be set to */
|
||||
private int mMax = 100;
|
||||
|
||||
public SeekArc(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
private OnSeekArcChangeListener mOnSeekArcChangeListener;
|
||||
/** The Current value that the SeekArc is set to */
|
||||
private int mProgress = 0;
|
||||
|
||||
private Paint mProgressPaint;
|
||||
private float mProgressSweep = 0;
|
||||
/** The width of the progress line for this SeekArc */
|
||||
private int mProgressWidth = 4;
|
||||
/** The rotation of the SeekArc- 0 is twelve o'clock */
|
||||
private int mRotation = 0;
|
||||
/** Give the SeekArc rounded edges */
|
||||
private boolean mRoundedEdges = false;
|
||||
/** The Angle to start drawing this Arc from */
|
||||
private int mStartAngle = 0;
|
||||
/** The Angle through which to draw the arc (Max is 360) */
|
||||
private int mSweepAngle = 360;
|
||||
/** The Drawable for the seek arc thumbnail */
|
||||
private Drawable mThumb;
|
||||
|
||||
private int mThumbXPos;
|
||||
private int mThumbYPos;
|
||||
private double mTouchAngle;
|
||||
private float mTouchIgnoreRadius;
|
||||
/** Enable touch inside the SeekArc */
|
||||
private boolean mTouchInside = true;
|
||||
|
||||
private int mTranslateX;
|
||||
private int mTranslateY;
|
||||
|
||||
public SeekArc(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
}
|
||||
|
||||
public SeekArc(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, R.attr.seekArcStyle);
|
||||
}
|
||||
|
||||
public SeekArc(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public int getArcColor() {
|
||||
return mArcPaint.getColor();
|
||||
}
|
||||
|
||||
public void setArcColor(int color) {
|
||||
mArcPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getArcRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public void setArcRotation(int mRotation) {
|
||||
this.mRotation = mRotation;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
public int getArcWidth() {
|
||||
return mArcWidth;
|
||||
}
|
||||
|
||||
public void setArcWidth(int mArcWidth) {
|
||||
this.mArcWidth = mArcWidth;
|
||||
mArcPaint.setStrokeWidth(mArcWidth);
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return mMax;
|
||||
}
|
||||
|
||||
public void setMax(int mMax) {
|
||||
this.mMax = mMax;
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
updateProgress(progress, false);
|
||||
}
|
||||
|
||||
public int getProgressColor() {
|
||||
return mProgressPaint.getColor();
|
||||
}
|
||||
|
||||
public void setProgressColor(int color) {
|
||||
mProgressPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getProgressWidth() {
|
||||
return mProgressWidth;
|
||||
}
|
||||
|
||||
public void setProgressWidth(int mProgressWidth) {
|
||||
this.mProgressWidth = mProgressWidth;
|
||||
mProgressPaint.setStrokeWidth(mProgressWidth);
|
||||
}
|
||||
|
||||
public int getStartAngle() {
|
||||
return mStartAngle;
|
||||
}
|
||||
|
||||
public void setStartAngle(int mStartAngle) {
|
||||
this.mStartAngle = mStartAngle;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
public int getSweepAngle() {
|
||||
return mSweepAngle;
|
||||
}
|
||||
|
||||
public void setSweepAngle(int mSweepAngle) {
|
||||
this.mSweepAngle = mSweepAngle;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
public boolean isClockwise() {
|
||||
return mClockwise;
|
||||
}
|
||||
|
||||
public void setClockwise(boolean isClockwise) {
|
||||
mClockwise = isClockwise;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.mEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mEnabled) {
|
||||
this.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
onStartTrackingTouch();
|
||||
updateOnTouch(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
updateOnTouch(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
onStopTrackingTouch();
|
||||
setPressed(false);
|
||||
this.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onStopTrackingTouch();
|
||||
setPressed(false);
|
||||
this.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to receive notifications of changes to the SeekArc's progress level. Also
|
||||
* provides notifications of when the user starts and stops a touch gesture within the SeekArc.
|
||||
*
|
||||
* @param l The seek bar notification listener
|
||||
* @see SeekArc.OnSeekBarChangeListener
|
||||
*/
|
||||
public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) {
|
||||
mOnSeekArcChangeListener = l;
|
||||
}
|
||||
|
||||
public void setRoundedEdges(boolean isEnabled) {
|
||||
mRoundedEdges = isEnabled;
|
||||
if (mRoundedEdges) {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
} else {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTouchInSide(boolean isEnabled) {
|
||||
int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
|
||||
int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
|
||||
mTouchInside = isEnabled;
|
||||
if (mTouchInside) {
|
||||
mTouchIgnoreRadius = (float) mArcRadius / 4;
|
||||
} else {
|
||||
// Don't use the exact radius makes interaction too tricky
|
||||
mTouchIgnoreRadius = mArcRadius - Math.min(thumbHalfWidth, thumbHalfheight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
if (mThumb != null && mThumb.isStateful()) {
|
||||
int[] state = getDrawableState();
|
||||
mThumb.setState(state);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (!mClockwise) {
|
||||
canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY());
|
||||
}
|
||||
|
||||
public SeekArc(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, R.attr.seekArcStyle);
|
||||
// Draw the arcs
|
||||
final int arcStart = mStartAngle + mAngleOffset + mRotation;
|
||||
final int arcSweep = mSweepAngle;
|
||||
canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint);
|
||||
canvas.drawArc(mArcRect, arcStart, mProgressSweep, false, mProgressPaint);
|
||||
|
||||
if (mEnabled) {
|
||||
// Draw the thumb nail
|
||||
canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos);
|
||||
mThumb.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
final int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
|
||||
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
|
||||
final int min = Math.min(width, height);
|
||||
float top = 0;
|
||||
float left = 0;
|
||||
int arcDiameter = 0;
|
||||
|
||||
mTranslateX = (int) (width * 0.5f);
|
||||
mTranslateY = (int) (height * 0.5f);
|
||||
|
||||
arcDiameter = min - getPaddingLeft();
|
||||
mArcRadius = arcDiameter / 2;
|
||||
top = height / 2 - (arcDiameter / 2);
|
||||
left = width / 2 - (arcDiameter / 2);
|
||||
mArcRect.set(left, top, left + arcDiameter, top + arcDiameter);
|
||||
|
||||
int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90;
|
||||
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart)));
|
||||
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart)));
|
||||
|
||||
setTouchInSide(mTouchInside);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private int getProgressForAngle(double angle) {
|
||||
int touchProgress = (int) Math.round(valuePerDegree() * angle);
|
||||
|
||||
touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE : touchProgress;
|
||||
touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE : touchProgress;
|
||||
return touchProgress;
|
||||
}
|
||||
|
||||
private double getTouchDegrees(float xPos, float yPos) {
|
||||
float x = xPos - mTranslateX;
|
||||
float y = yPos - mTranslateY;
|
||||
// invert the x-coord if we are rotating anti-clockwise
|
||||
x = (mClockwise) ? x : -x;
|
||||
// convert to arc Angle
|
||||
double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2) - Math.toRadians(mRotation));
|
||||
if (angle < 0) {
|
||||
angle = 360 + angle;
|
||||
}
|
||||
angle -= mStartAngle;
|
||||
return angle;
|
||||
}
|
||||
|
||||
private boolean ignoreTouch(float xPos, float yPos) {
|
||||
boolean ignore = false;
|
||||
float x = xPos - mTranslateX;
|
||||
float y = yPos - mTranslateY;
|
||||
|
||||
float touchRadius = (float) Math.sqrt(((x * x) + (y * y)));
|
||||
if (touchRadius < mTouchIgnoreRadius) {
|
||||
ignore = true;
|
||||
}
|
||||
return ignore;
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||
|
||||
Log.d(TAG, "Initialising SeekArc");
|
||||
final Resources res = getResources();
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
// Defaults, may need to link this into theme settings
|
||||
int arcColor = res.getColor(R.color.progress_gray);
|
||||
int progressColor = res.getColor(R.color.default_blue_light);
|
||||
int thumbHalfheight = 0;
|
||||
int thumbHalfWidth = 0;
|
||||
mThumb = res.getDrawable(R.drawable.switch_thumb_material);
|
||||
// Convert progress width to pixels for current density
|
||||
mProgressWidth = (int) (mProgressWidth * density);
|
||||
|
||||
if (attrs != null) {
|
||||
// Attribute initialization
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekArc, defStyle, 0);
|
||||
|
||||
Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb);
|
||||
if (thumb != null) {
|
||||
mThumb = thumb;
|
||||
}
|
||||
|
||||
thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
|
||||
thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
|
||||
mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth, thumbHalfheight);
|
||||
|
||||
mMax = a.getInteger(R.styleable.SeekArc_max, mMax);
|
||||
mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress);
|
||||
mProgressWidth = (int) a.getDimension(R.styleable.SeekArc_progressWidth, mProgressWidth);
|
||||
mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth, mArcWidth);
|
||||
mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle);
|
||||
mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle);
|
||||
mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation);
|
||||
mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges, mRoundedEdges);
|
||||
mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside, mTouchInside);
|
||||
mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise, mClockwise);
|
||||
mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled);
|
||||
|
||||
arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor);
|
||||
progressColor = a.getColor(R.styleable.SeekArc_progressColor, progressColor);
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public SeekArc(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs, defStyle);
|
||||
mProgress = (mProgress > mMax) ? mMax : mProgress;
|
||||
mProgress = (mProgress < 0) ? 0 : mProgress;
|
||||
|
||||
mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle;
|
||||
mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle;
|
||||
|
||||
mProgressSweep = (float) mProgress / mMax * mSweepAngle;
|
||||
|
||||
mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle;
|
||||
mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle;
|
||||
|
||||
mArcPaint = new Paint();
|
||||
mArcPaint.setColor(arcColor);
|
||||
mArcPaint.setAntiAlias(true);
|
||||
mArcPaint.setStyle(Paint.Style.STROKE);
|
||||
mArcPaint.setStrokeWidth(mArcWidth);
|
||||
// mArcPaint.setAlpha(45);
|
||||
|
||||
mProgressPaint = new Paint();
|
||||
mProgressPaint.setColor(progressColor);
|
||||
mProgressPaint.setAntiAlias(true);
|
||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
||||
mProgressPaint.setStrokeWidth(mProgressWidth);
|
||||
|
||||
if (mRoundedEdges) {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
private void onProgressRefresh(int progress, boolean fromUser) {
|
||||
updateProgress(progress, fromUser);
|
||||
}
|
||||
|
||||
private void onStartTrackingTouch() {
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener.onStartTrackingTouch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void onStopTrackingTouch() {
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener.onStopTrackingTouch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnTouch(MotionEvent event) {
|
||||
boolean ignoreTouch = ignoreTouch(event.getX(), event.getY());
|
||||
if (ignoreTouch) {
|
||||
return;
|
||||
}
|
||||
setPressed(true);
|
||||
mTouchAngle = getTouchDegrees(event.getX(), event.getY());
|
||||
int progress = getProgressForAngle(mTouchAngle);
|
||||
onProgressRefresh(progress, true);
|
||||
}
|
||||
|
||||
private void updateProgress(int progress, boolean fromUser) {
|
||||
|
||||
if (progress == INVALID_PROGRESS_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
public int getArcColor() {
|
||||
return mArcPaint.getColor();
|
||||
progress = (progress > mMax) ? mMax : progress;
|
||||
progress = (progress < 0) ? 0 : progress;
|
||||
mProgress = progress;
|
||||
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener.onProgressChanged(this, progress, fromUser);
|
||||
}
|
||||
|
||||
public void setArcColor(int color) {
|
||||
mArcPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
mProgressSweep = (float) progress / mMax * mSweepAngle;
|
||||
|
||||
public int getArcRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
updateThumbPosition();
|
||||
|
||||
public void setArcRotation(int mRotation) {
|
||||
this.mRotation = mRotation;
|
||||
updateThumbPosition();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getArcWidth() {
|
||||
return mArcWidth;
|
||||
}
|
||||
private void updateThumbPosition() {
|
||||
int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90);
|
||||
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle)));
|
||||
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle)));
|
||||
}
|
||||
|
||||
public void setArcWidth(int mArcWidth) {
|
||||
this.mArcWidth = mArcWidth;
|
||||
mArcPaint.setStrokeWidth(mArcWidth);
|
||||
}
|
||||
private float valuePerDegree() {
|
||||
return (float) mMax / mSweepAngle;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return mMax;
|
||||
}
|
||||
|
||||
public void setMax(int mMax) {
|
||||
this.mMax = mMax;
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
updateProgress(progress, false);
|
||||
}
|
||||
|
||||
public int getProgressColor() {
|
||||
return mProgressPaint.getColor();
|
||||
}
|
||||
|
||||
public void setProgressColor(int color) {
|
||||
mProgressPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getProgressWidth() {
|
||||
return mProgressWidth;
|
||||
}
|
||||
|
||||
public void setProgressWidth(int mProgressWidth) {
|
||||
this.mProgressWidth = mProgressWidth;
|
||||
mProgressPaint.setStrokeWidth(mProgressWidth);
|
||||
}
|
||||
|
||||
public int getStartAngle() {
|
||||
return mStartAngle;
|
||||
}
|
||||
|
||||
public void setStartAngle(int mStartAngle) {
|
||||
this.mStartAngle = mStartAngle;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
public int getSweepAngle() {
|
||||
return mSweepAngle;
|
||||
}
|
||||
|
||||
public void setSweepAngle(int mSweepAngle) {
|
||||
this.mSweepAngle = mSweepAngle;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
public boolean isClockwise() {
|
||||
return mClockwise;
|
||||
}
|
||||
|
||||
public void setClockwise(boolean isClockwise) {
|
||||
mClockwise = isClockwise;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.mEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mEnabled) {
|
||||
this.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
onStartTrackingTouch();
|
||||
updateOnTouch(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
updateOnTouch(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
onStopTrackingTouch();
|
||||
setPressed(false);
|
||||
this.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onStopTrackingTouch();
|
||||
setPressed(false);
|
||||
this.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public interface OnSeekArcChangeListener {
|
||||
|
||||
/**
|
||||
* Sets a listener to receive notifications of changes to the SeekArc's
|
||||
* progress level. Also provides notifications of when the user starts and
|
||||
* stops a touch gesture within the SeekArc.
|
||||
* Notification that the progress level has changed. Clients can use the fromUser parameter to
|
||||
* distinguish user-initiated changes from those that occurred programmatically.
|
||||
*
|
||||
* @param l The seek bar notification listener
|
||||
* @see SeekArc.OnSeekBarChangeListener
|
||||
* @param seekArc The SeekArc whose progress has changed
|
||||
* @param progress The current progress level. This will be in the range 0..max where max was
|
||||
* set by {@link ProgressArc#setMax(int)}. (The default value for max is 100.)
|
||||
* @param fromUser True if the progress change was initiated by the user.
|
||||
*/
|
||||
public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) {
|
||||
mOnSeekArcChangeListener = l;
|
||||
}
|
||||
void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser);
|
||||
|
||||
public void setRoundedEdges(boolean isEnabled) {
|
||||
mRoundedEdges = isEnabled;
|
||||
if (mRoundedEdges) {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
} else {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Notification that the user has started a touch gesture. Clients may want to use this to
|
||||
* disable advancing the seekbar.
|
||||
*
|
||||
* @param seekArc The SeekArc in which the touch gesture began
|
||||
*/
|
||||
void onStartTrackingTouch(SeekArc seekArc);
|
||||
|
||||
public void setTouchInSide(boolean isEnabled) {
|
||||
int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
|
||||
int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
|
||||
mTouchInside = isEnabled;
|
||||
if (mTouchInside) {
|
||||
mTouchIgnoreRadius = (float) mArcRadius / 4;
|
||||
} else {
|
||||
// Don't use the exact radius makes interaction too tricky
|
||||
mTouchIgnoreRadius = mArcRadius
|
||||
- Math.min(thumbHalfWidth, thumbHalfheight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
if (mThumb != null && mThumb.isStateful()) {
|
||||
int[] state = getDrawableState();
|
||||
mThumb.setState(state);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (!mClockwise) {
|
||||
canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY());
|
||||
}
|
||||
|
||||
// Draw the arcs
|
||||
final int arcStart = mStartAngle + mAngleOffset + mRotation;
|
||||
final int arcSweep = mSweepAngle;
|
||||
canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint);
|
||||
canvas.drawArc(mArcRect, arcStart, mProgressSweep, false,
|
||||
mProgressPaint);
|
||||
|
||||
if (mEnabled) {
|
||||
// Draw the thumb nail
|
||||
canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos);
|
||||
mThumb.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
final int height = getDefaultSize(getSuggestedMinimumHeight(),
|
||||
heightMeasureSpec);
|
||||
final int width = getDefaultSize(getSuggestedMinimumWidth(),
|
||||
widthMeasureSpec);
|
||||
final int min = Math.min(width, height);
|
||||
float top = 0;
|
||||
float left = 0;
|
||||
int arcDiameter = 0;
|
||||
|
||||
mTranslateX = (int) (width * 0.5f);
|
||||
mTranslateY = (int) (height * 0.5f);
|
||||
|
||||
arcDiameter = min - getPaddingLeft();
|
||||
mArcRadius = arcDiameter / 2;
|
||||
top = height / 2 - (arcDiameter / 2);
|
||||
left = width / 2 - (arcDiameter / 2);
|
||||
mArcRect.set(left, top, left + arcDiameter, top + arcDiameter);
|
||||
|
||||
int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90;
|
||||
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart)));
|
||||
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart)));
|
||||
|
||||
setTouchInSide(mTouchInside);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private int getProgressForAngle(double angle) {
|
||||
int touchProgress = (int) Math.round(valuePerDegree() * angle);
|
||||
|
||||
touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE
|
||||
: touchProgress;
|
||||
touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE
|
||||
: touchProgress;
|
||||
return touchProgress;
|
||||
}
|
||||
|
||||
private double getTouchDegrees(float xPos, float yPos) {
|
||||
float x = xPos - mTranslateX;
|
||||
float y = yPos - mTranslateY;
|
||||
//invert the x-coord if we are rotating anti-clockwise
|
||||
x = (mClockwise) ? x : -x;
|
||||
// convert to arc Angle
|
||||
double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2)
|
||||
- Math.toRadians(mRotation));
|
||||
if (angle < 0) {
|
||||
angle = 360 + angle;
|
||||
}
|
||||
angle -= mStartAngle;
|
||||
return angle;
|
||||
}
|
||||
|
||||
private boolean ignoreTouch(float xPos, float yPos) {
|
||||
boolean ignore = false;
|
||||
float x = xPos - mTranslateX;
|
||||
float y = yPos - mTranslateY;
|
||||
|
||||
float touchRadius = (float) Math.sqrt(((x * x) + (y * y)));
|
||||
if (touchRadius < mTouchIgnoreRadius) {
|
||||
ignore = true;
|
||||
}
|
||||
return ignore;
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||
|
||||
Log.d(TAG, "Initialising SeekArc");
|
||||
final Resources res = getResources();
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
// Defaults, may need to link this into theme settings
|
||||
int arcColor = res.getColor(R.color.progress_gray);
|
||||
int progressColor = res.getColor(R.color.default_blue_light);
|
||||
int thumbHalfheight = 0;
|
||||
int thumbHalfWidth = 0;
|
||||
mThumb = res.getDrawable(R.drawable.switch_thumb_material);
|
||||
// Convert progress width to pixels for current density
|
||||
mProgressWidth = (int) (mProgressWidth * density);
|
||||
|
||||
if (attrs != null) {
|
||||
// Attribute initialization
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.SeekArc, defStyle, 0);
|
||||
|
||||
Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb);
|
||||
if (thumb != null) {
|
||||
mThumb = thumb;
|
||||
}
|
||||
|
||||
thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
|
||||
thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
|
||||
mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth,
|
||||
thumbHalfheight);
|
||||
|
||||
mMax = a.getInteger(R.styleable.SeekArc_max, mMax);
|
||||
mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress);
|
||||
mProgressWidth = (int) a.getDimension(
|
||||
R.styleable.SeekArc_progressWidth, mProgressWidth);
|
||||
mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth,
|
||||
mArcWidth);
|
||||
mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle);
|
||||
mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle);
|
||||
mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation);
|
||||
mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges,
|
||||
mRoundedEdges);
|
||||
mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside,
|
||||
mTouchInside);
|
||||
mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise,
|
||||
mClockwise);
|
||||
mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled);
|
||||
|
||||
arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor);
|
||||
progressColor = a.getColor(R.styleable.SeekArc_progressColor,
|
||||
progressColor);
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
mProgress = (mProgress > mMax) ? mMax : mProgress;
|
||||
mProgress = (mProgress < 0) ? 0 : mProgress;
|
||||
|
||||
mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle;
|
||||
mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle;
|
||||
|
||||
mProgressSweep = (float) mProgress / mMax * mSweepAngle;
|
||||
|
||||
mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle;
|
||||
mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle;
|
||||
|
||||
mArcPaint = new Paint();
|
||||
mArcPaint.setColor(arcColor);
|
||||
mArcPaint.setAntiAlias(true);
|
||||
mArcPaint.setStyle(Paint.Style.STROKE);
|
||||
mArcPaint.setStrokeWidth(mArcWidth);
|
||||
//mArcPaint.setAlpha(45);
|
||||
|
||||
mProgressPaint = new Paint();
|
||||
mProgressPaint.setColor(progressColor);
|
||||
mProgressPaint.setAntiAlias(true);
|
||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
||||
mProgressPaint.setStrokeWidth(mProgressWidth);
|
||||
|
||||
if (mRoundedEdges) {
|
||||
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
private void onProgressRefresh(int progress, boolean fromUser) {
|
||||
updateProgress(progress, fromUser);
|
||||
}
|
||||
|
||||
private void onStartTrackingTouch() {
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener.onStartTrackingTouch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void onStopTrackingTouch() {
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener.onStopTrackingTouch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnTouch(MotionEvent event) {
|
||||
boolean ignoreTouch = ignoreTouch(event.getX(), event.getY());
|
||||
if (ignoreTouch) {
|
||||
return;
|
||||
}
|
||||
setPressed(true);
|
||||
mTouchAngle = getTouchDegrees(event.getX(), event.getY());
|
||||
int progress = getProgressForAngle(mTouchAngle);
|
||||
onProgressRefresh(progress, true);
|
||||
}
|
||||
|
||||
private void updateProgress(int progress, boolean fromUser) {
|
||||
|
||||
if (progress == INVALID_PROGRESS_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress = (progress > mMax) ? mMax : progress;
|
||||
progress = (progress < 0) ? 0 : progress;
|
||||
mProgress = progress;
|
||||
|
||||
if (mOnSeekArcChangeListener != null) {
|
||||
mOnSeekArcChangeListener
|
||||
.onProgressChanged(this, progress, fromUser);
|
||||
}
|
||||
|
||||
mProgressSweep = (float) progress / mMax * mSweepAngle;
|
||||
|
||||
updateThumbPosition();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void updateThumbPosition() {
|
||||
int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90);
|
||||
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle)));
|
||||
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle)));
|
||||
}
|
||||
|
||||
private float valuePerDegree() {
|
||||
return (float) mMax / mSweepAngle;
|
||||
}
|
||||
|
||||
public interface OnSeekArcChangeListener {
|
||||
|
||||
/**
|
||||
* Notification that the progress level has changed. Clients can use the
|
||||
* fromUser parameter to distinguish user-initiated changes from those
|
||||
* that occurred programmatically.
|
||||
*
|
||||
* @param seekArc The SeekArc whose progress has changed
|
||||
* @param progress The current progress level. This will be in the range
|
||||
* 0..max where max was set by
|
||||
* {@link ProgressArc#setMax(int)}. (The default value for
|
||||
* max is 100.)
|
||||
* @param fromUser True if the progress change was initiated by the user.
|
||||
*/
|
||||
void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser);
|
||||
|
||||
/**
|
||||
* Notification that the user has started a touch gesture. Clients may
|
||||
* want to use this to disable advancing the seekbar.
|
||||
*
|
||||
* @param seekArc The SeekArc in which the touch gesture began
|
||||
*/
|
||||
void onStartTrackingTouch(SeekArc seekArc);
|
||||
|
||||
/**
|
||||
* Notification that the user has finished a touch gesture. Clients may
|
||||
* want to use this to re-enable advancing the seekarc.
|
||||
*
|
||||
* @param seekArc The SeekArc in which the touch gesture began
|
||||
*/
|
||||
void onStopTrackingTouch(SeekArc seekArc);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Notification that the user has finished a touch gesture. Clients may want to use this to
|
||||
* re-enable advancing the seekarc.
|
||||
*
|
||||
* @param seekArc The SeekArc in which the touch gesture began
|
||||
*/
|
||||
void onStopTrackingTouch(SeekArc seekArc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,33 +19,32 @@ import android.os.Build;
|
|||
import android.util.AttributeSet;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class StatusBarMarginFrameLayout extends FrameLayout {
|
||||
|
||||
public StatusBarMarginFrameLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public StatusBarMarginFrameLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public StatusBarMarginFrameLayout(
|
||||
@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
|
||||
lp.topMargin = insets.getSystemWindowInsetTop();
|
||||
lp.bottomMargin = insets.getSystemWindowInsetBottom();
|
||||
setLayoutParams(lp);
|
||||
}
|
||||
return super.onApplyWindowInsets(insets);
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
|
||||
lp.topMargin = insets.getSystemWindowInsetTop();
|
||||
lp.bottomMargin = insets.getSystemWindowInsetBottom();
|
||||
setLayoutParams(lp);
|
||||
}
|
||||
return super.onApplyWindowInsets(insets);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,42 +18,38 @@ import android.content.Context;
|
|||
import android.content.res.Resources;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class StatusBarView extends View {
|
||||
|
||||
public StatusBarView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public StatusBarView(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public static int getStatusBarHeight(@NonNull Resources r) {
|
||||
int result = 0;
|
||||
int resourceId = r.getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
result = r.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
private void init(Context context) {}
|
||||
|
||||
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public static int getStatusBarHeight(@NonNull Resources r) {
|
||||
int result = 0;
|
||||
int resourceId = r.getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
result = r.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources()));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,50 +19,46 @@ import android.graphics.Canvas;
|
|||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
|
||||
public class VerticalTextView extends AppCompatTextView {
|
||||
final boolean topDown;
|
||||
final boolean topDown;
|
||||
|
||||
public VerticalTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
final int gravity = getGravity();
|
||||
if (Gravity.isVertical(gravity) && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
|
||||
setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
|
||||
topDown = false;
|
||||
} else
|
||||
topDown = true;
|
||||
public VerticalTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
final int gravity = getGravity();
|
||||
if (Gravity.isVertical(gravity)
|
||||
&& (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
|
||||
setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
|
||||
topDown = false;
|
||||
} else topDown = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
TextPaint textPaint = getPaint();
|
||||
textPaint.setColor(getCurrentTextColor());
|
||||
textPaint.drawableState = getDrawableState();
|
||||
|
||||
canvas.save();
|
||||
|
||||
if (topDown) {
|
||||
canvas.translate(getWidth(), 0);
|
||||
canvas.rotate(90);
|
||||
} else {
|
||||
canvas.translate(0, getHeight());
|
||||
canvas.rotate(-90);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
}
|
||||
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
TextPaint textPaint = getPaint();
|
||||
textPaint.setColor(getCurrentTextColor());
|
||||
textPaint.drawableState = getDrawableState();
|
||||
|
||||
canvas.save();
|
||||
|
||||
if (topDown) {
|
||||
canvas.translate(getWidth(), 0);
|
||||
canvas.rotate(90);
|
||||
} else {
|
||||
canvas.translate(0, getHeight());
|
||||
canvas.rotate(-90);
|
||||
}
|
||||
|
||||
|
||||
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
|
||||
|
||||
getLayout().draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
getLayout().draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue