Initial commit retro music app
This commit is contained in:
parent
ab332473bc
commit
fe890632fd
932 changed files with 83126 additions and 0 deletions
|
@ -0,0 +1,51 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
*/
|
||||
public class AutofitRecyclerView extends RecyclerView {
|
||||
private GridLayoutManager manager;
|
||||
private int columnWidth = -1; //default value
|
||||
|
||||
public AutofitRecyclerView(Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public AutofitRecyclerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
if (attrs != null) {
|
||||
int[] attrsArray = {android.R.attr.columnWidth};
|
||||
TypedArray array = context.obtainStyledAttributes(attrs, attrsArray);
|
||||
columnWidth = array.getDimensionPixelSize(0, -1);
|
||||
array.recycle();
|
||||
}
|
||||
|
||||
manager = new GridLayoutManager(getContext(), 1, GridLayoutManager.HORIZONTAL,false);
|
||||
setLayoutManager(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
if (columnWidth > 0) {
|
||||
int spanCount = Math.max(1, getMeasuredWidth() / columnWidth);
|
||||
manager.setSpanCount(spanCount);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,41 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class BottomSheetListView extends ListView {
|
||||
public BottomSheetListView(Context context, AttributeSet p_attrs) {
|
||||
super(context, p_attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (canScrollVertically(this)) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
public boolean canScrollVertically(AbsListView view) {
|
||||
boolean canScroll = false;
|
||||
|
||||
if (view != null && view.getChildCount() > 0) {
|
||||
boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0;
|
||||
boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount();
|
||||
|
||||
if (isOnTop || isAllItemsVisible) {
|
||||
canScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
return canScroll;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,417 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import code.name.monkey.appthemehelper.ThemeStore;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
public static class Crumb implements Parcelable {
|
||||
|
||||
public Crumb(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private final File file;
|
||||
private int scrollPos;
|
||||
|
||||
public int getScrollPosition() {
|
||||
return scrollPos;
|
||||
}
|
||||
|
||||
public void setScrollPosition(int scrollY) {
|
||||
this.scrollPos = scrollY;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return file.getPath().equals("/") ? "root" : file.getName();
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof Crumb) && ((Crumb) o).getFile() != null &&
|
||||
((Crumb) o).getFile().equals(getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Crumb{" +
|
||||
"file=" + file +
|
||||
", scrollPos=" + scrollPos +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(this.file);
|
||||
dest.writeInt(this.scrollPos);
|
||||
}
|
||||
|
||||
protected Crumb(Parcel in) {
|
||||
this.file = (File) in.readSerializable();
|
||||
this.scrollPos = in.readInt();
|
||||
}
|
||||
|
||||
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 interface SelectionCallback {
|
||||
void onCrumbSelection(Crumb crumb, int index);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Stores currently visible crumbs
|
||||
private List<Crumb> mCrumbs;
|
||||
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
|
||||
private List<Crumb> mOldCrumbs;
|
||||
// Stores user's navigation history, like a fragment back stack
|
||||
private List<Crumb> mHistory;
|
||||
|
||||
private LinearLayout mChildFrame;
|
||||
private int mActive;
|
||||
private SelectionCallback mCallback;
|
||||
|
||||
private void init() {
|
||||
contentColorActivated = ThemeStore.textColorPrimary(getContext());
|
||||
contentColorDeactivated = ThemeStore.textColorSecondary(getContext());
|
||||
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 void addHistory(Crumb crumb) {
|
||||
mHistory.add(crumb);
|
||||
}
|
||||
|
||||
public Crumb lastHistory() {
|
||||
if (mHistory.size() == 0) return null;
|
||||
return mHistory.get(mHistory.size() - 1);
|
||||
}
|
||||
|
||||
public boolean popHistory() {
|
||||
if (mHistory.size() == 0) return false;
|
||||
mHistory.remove(mHistory.size() - 1);
|
||||
return mHistory.size() != 0;
|
||||
}
|
||||
|
||||
public int historySize() {
|
||||
return mHistory.size();
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
mHistory.clear();
|
||||
}
|
||||
|
||||
public void reverseHistory() {
|
||||
Collections.reverse(mHistory);
|
||||
}
|
||||
|
||||
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 (Build.VERSION.SDK_INT >= 19 && 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();
|
||||
}
|
||||
|
||||
@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 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 clearCrumbs() {
|
||||
try {
|
||||
mOldCrumbs = new ArrayList<>(mCrumbs);
|
||||
mCrumbs.clear();
|
||||
mChildFrame.removeAllViews();
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Crumb getCrumb(int index) {
|
||||
return mCrumbs.get(index);
|
||||
}
|
||||
|
||||
public void setCallback(SelectionCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
private boolean setActive(Crumb newActive) {
|
||||
mActive = mCrumbs.indexOf(newActive);
|
||||
invalidateActivatedAll();
|
||||
boolean success = mActive > -1;
|
||||
if (success)
|
||||
requestLayout();
|
||||
return success;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void updateIndices() {
|
||||
for (int i = 0; i < mChildFrame.getChildCount(); i++)
|
||||
mChildFrame.getChildAt(i).setTag(i);
|
||||
}
|
||||
|
||||
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 int size() {
|
||||
return mCrumbs.size();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getActiveIndex() {
|
||||
return mActive;
|
||||
}
|
||||
|
||||
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
|
||||
this.contentColorActivated = contentColorActivated;
|
||||
}
|
||||
|
||||
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
|
||||
this.contentColorDeactivated = contentColorDeactivated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCallback != null) {
|
||||
int index = (Integer) v.getTag();
|
||||
mCallback.onCrumbSelection(mCrumbs.get(index), index);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SavedStateWrapper implements Parcelable {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
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 SavedStateWrapper getStateWrapper() {
|
||||
return new SavedStateWrapper(this);
|
||||
}
|
||||
|
||||
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
|
||||
if (mSavedState != null) {
|
||||
mActive = mSavedState.mActive;
|
||||
for (Crumb c : mSavedState.mCrumbs) {
|
||||
addCrumb(c, false);
|
||||
}
|
||||
requestLayout();
|
||||
setVisibility(mSavedState.mVisibility);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class CircularImageView extends AppCompatImageView {
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
//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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class HeightFitSquareLayout extends FrameLayout {
|
||||
private boolean forceSquare = true;
|
||||
|
||||
public HeightFitSquareLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HeightFitSquareLayout(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
|
||||
public class IconImageView extends android.support.v7.widget.AppCompatImageView {
|
||||
public IconImageView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public IconImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public IconImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
if (context == null) return;
|
||||
setColorFilter(ATHUtil.resolveColor(context, R.attr.iconColor), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,876 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import org.mozilla.universalchardet.UniversalDetector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* Created by zhengken.me on 2016/11/27.
|
||||
* ClassName : LyricView
|
||||
* Description :
|
||||
*/
|
||||
public class LyricView extends View {
|
||||
|
||||
public static final int LEFT = 0;
|
||||
public static final int CENTER = 1;
|
||||
public static final int RIGHT = 2;
|
||||
private static final String TAG = "LyricView";
|
||||
private static final float SLIDE_COEFFICIENT = 0.2f;
|
||||
|
||||
private static final int UNITS_SECOND = 1000;
|
||||
private static final int UNITS_MILLISECOND = 1;
|
||||
|
||||
private static final int FLING_ANIMATOR_DURATION = 500 * UNITS_MILLISECOND;
|
||||
|
||||
private static final int THRESHOLD_Y_VELOCITY = 1600;
|
||||
|
||||
private static final int INDICATOR_ICON_PLAY_MARGIN_LEFT = 7;//dp
|
||||
private static final int INDICATOR_ICON_PLAY_WIDTH = 15;//sp
|
||||
private static final int INDICATOR_LINE_MARGIN = 10;//dp
|
||||
private static final int INDICATOR_TIME_TEXT_SIZE = 10;//sp
|
||||
private static final int INDICATOR_TIME_MARGIN_RIGHT = 7;//dp
|
||||
|
||||
private static final int DEFAULT_TEXT_SIZE = 16;//sp
|
||||
private static final int DEFAULT_MAX_LENGTH = 300;//dp
|
||||
private static final int DEFAULT_LINE_SPACE = 25;//dp
|
||||
|
||||
private int mHintColor;
|
||||
private int mDefaultColor;
|
||||
private int mHighLightColor;
|
||||
private int mTextAlign;
|
||||
|
||||
|
||||
private int mLineCount;
|
||||
private int mTextSize;
|
||||
private float mLineHeight;
|
||||
private LyricInfo mLyricInfo;
|
||||
private String mDefaultHint;
|
||||
private int mMaxLength;
|
||||
|
||||
private TextPaint mTextPaint;
|
||||
private Paint mBtnPlayPaint;
|
||||
private Paint mLinePaint;
|
||||
private Paint mTimerPaint;
|
||||
|
||||
private boolean mFling = false;
|
||||
private ValueAnimator mFlingAnimator;
|
||||
private float mScrollY = 0;
|
||||
private float mLineSpace = 0;
|
||||
private boolean mIsShade;
|
||||
private float mShaderWidth = 0;
|
||||
private int mCurrentPlayLine = 0;
|
||||
private boolean mShowIndicator;
|
||||
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private float mVelocity = 0;
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
private float mLastScrollY;
|
||||
private boolean mUserTouch = false;
|
||||
Runnable hideIndicator = () -> {
|
||||
setUserTouch(false);
|
||||
invalidateView();
|
||||
};
|
||||
private int maxVelocity;
|
||||
private int mLineNumberUnderIndicator = 0;
|
||||
private Rect mBtnPlayRect = new Rect();
|
||||
private Rect mTimerRect;
|
||||
private String mDefaultTime = "00:00";
|
||||
private int mLineColor = Color.parseColor("#EFEFEF");
|
||||
private int mBtnColor = Color.parseColor("#EFEFEF");
|
||||
private List<Integer> mLineFeedRecord = new ArrayList<>();
|
||||
private boolean mEnableLineFeed = false;
|
||||
private int mExtraHeight = 0;
|
||||
private int mTextHeight;
|
||||
private String mCurrentLyricFilePath = null;
|
||||
private OnPlayerClickListener mClickListener;
|
||||
|
||||
public LyricView(Context context) {
|
||||
super(context);
|
||||
initMyView(context);
|
||||
}
|
||||
|
||||
public LyricView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
getAttrs(context, attributeSet);
|
||||
initMyView(context);
|
||||
|
||||
}
|
||||
|
||||
public LyricView(Context context, AttributeSet attributeSet, int i) {
|
||||
super(context, attributeSet, i);
|
||||
getAttrs(context, attributeSet);
|
||||
initMyView(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(event);
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
actionCancel(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
actionDown(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
actionMove(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
actionUp(event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
invalidateView();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
mBtnPlayRect.set((int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT),
|
||||
(int) (getHeight() * 0.5f - getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f),
|
||||
(int) (getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT)),
|
||||
(int) (getHeight() * 0.5f + getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f));
|
||||
mShaderWidth = getWidth() * 0.4f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (scrollable()) {
|
||||
if (mShowIndicator) {
|
||||
drawIndicator(canvas);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mLineCount; i++) {
|
||||
float x = 0;
|
||||
switch (mTextAlign) {
|
||||
case LEFT:
|
||||
x = INDICATOR_ICON_PLAY_MARGIN_LEFT + INDICATOR_LINE_MARGIN + mBtnPlayRect.width();
|
||||
break;
|
||||
case CENTER:
|
||||
x = getWidth() * 0.5f;
|
||||
break;
|
||||
case RIGHT:
|
||||
x = getWidth() - INDICATOR_LINE_MARGIN * 2 - mTimerRect.width() - INDICATOR_ICON_PLAY_MARGIN_LEFT;
|
||||
break;
|
||||
}
|
||||
|
||||
float y;
|
||||
if (mEnableLineFeed && i > 0) {
|
||||
y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY + mLineFeedRecord.get(i - 1);
|
||||
} else {
|
||||
y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY;
|
||||
}
|
||||
|
||||
// float y = getHeight() * 0.5f + i * mLineHeight - mScrollY;
|
||||
if (y < 0) {
|
||||
continue;
|
||||
}
|
||||
if (y > getHeight()) {
|
||||
break;
|
||||
}
|
||||
if (i == mCurrentPlayLine - 1) {
|
||||
mTextPaint.setColor(mHighLightColor);
|
||||
} else if (i == mLineNumberUnderIndicator && mShowIndicator) {
|
||||
mTextPaint.setColor(Color.LTGRAY);
|
||||
} else {
|
||||
mTextPaint.setColor(mDefaultColor);
|
||||
}
|
||||
if (mIsShade && (y > getHeight() - mShaderWidth || y < mShaderWidth)) {
|
||||
if (y < mShaderWidth) {
|
||||
mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f));
|
||||
} else {
|
||||
mTextPaint.setAlpha(26 + (int) (23000.0f * (getHeight() - y) / mShaderWidth * 0.01f));
|
||||
}
|
||||
} else {
|
||||
mTextPaint.setAlpha(255);
|
||||
}
|
||||
|
||||
if (mEnableLineFeed) {
|
||||
StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint,
|
||||
mMaxLength,
|
||||
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
||||
canvas.save();
|
||||
canvas.translate(x, y);
|
||||
staticLayout.draw(canvas);
|
||||
canvas.restore();
|
||||
} else {
|
||||
canvas.drawText(mLyricInfo.songLines.get(i).content, x, y, mTextPaint);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mTextPaint.setColor(mHintColor);
|
||||
canvas.drawText(mDefaultHint, getMeasuredWidth() / 2, getMeasuredHeight() / 2, mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void getAttrs(Context context, AttributeSet attrs) {
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LyricView);
|
||||
mIsShade = ta.getBoolean(R.styleable.LyricView_fadeInFadeOut, false);
|
||||
mDefaultHint = ta.getString(R.styleable.LyricView_hint) != null
|
||||
? ta.getString(R.styleable.LyricView_hint)
|
||||
: getResources().getString(R.string.default_hint);
|
||||
mHintColor = ta.getColor(R.styleable.LyricView_hintColor, Color.parseColor("#FFFFFF"));
|
||||
mDefaultColor = ta.getColor(R.styleable.LyricView_textColor, Color.parseColor("#8D8D8D"));
|
||||
mHighLightColor = ta.getColor(R.styleable.LyricView_highlightColor, Color.parseColor("#FFFFFF"));
|
||||
mTextSize = ta.getDimensionPixelSize(R.styleable.LyricView_textSize, (int) getRawSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE));
|
||||
mTextAlign = ta.getInt(R.styleable.LyricView_textAlign, CENTER);
|
||||
mMaxLength = ta.getDimensionPixelSize(R.styleable.LyricView_maxLength, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH));
|
||||
mLineSpace = ta.getDimensionPixelSize(R.styleable.LyricView_lineSpace, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LINE_SPACE));
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public void setShade(boolean shade) {
|
||||
mIsShade = shade;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setHintColor(int hintColor) {
|
||||
mHintColor = hintColor;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setDefaultColor(int defaultColor) {
|
||||
mDefaultColor = defaultColor;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setHighLightColor(int highLightColor) {
|
||||
mHighLightColor = highLightColor;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setTextAlign(int textAlign) {
|
||||
mTextAlign = textAlign;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setLineCount(int lineCount) {
|
||||
mLineCount = lineCount;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setTextSize(int textSize) {
|
||||
mTextSize = textSize;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setDefaultHint(String defaultHint) {
|
||||
mDefaultHint = defaultHint;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setMaxLength(int maxLength) {
|
||||
mMaxLength = maxLength;
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
public void setOnPlayerClickListener(OnPlayerClickListener mClickListener) {
|
||||
this.mClickListener = mClickListener;
|
||||
}
|
||||
|
||||
public void setAlignment(@Alignment int alignment) {
|
||||
mTextAlign = alignment;
|
||||
}
|
||||
|
||||
public void setCurrentTimeMillis(long current) {
|
||||
scrollToCurrentTimeMillis(current);
|
||||
}
|
||||
|
||||
public void setLyricFile(File file) {
|
||||
|
||||
if (file == null || !file.exists()) {
|
||||
reset();
|
||||
mCurrentLyricFilePath = "";
|
||||
return;
|
||||
} else if (file.getPath().equals(mCurrentLyricFilePath)) {
|
||||
return;
|
||||
} else {
|
||||
mCurrentLyricFilePath = file.getPath();
|
||||
reset();
|
||||
}
|
||||
try {
|
||||
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] buf = new byte[1024];
|
||||
UniversalDetector detector = new UniversalDetector(null);
|
||||
int nread;
|
||||
while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
|
||||
detector.handleData(buf, 0, nread);
|
||||
}
|
||||
|
||||
detector.dataEnd();
|
||||
String encoding = detector.getDetectedCharset();
|
||||
if (encoding != null) {
|
||||
setLyricFile(file, encoding);
|
||||
} else {
|
||||
setLyricFile(file, "UTF-8");
|
||||
}
|
||||
detector.reset();
|
||||
fis.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void setLyricFile(File file, String charsetName) {
|
||||
if (file != null && file.exists()) {
|
||||
try {
|
||||
setupLyricResource(new FileInputStream(file), charsetName);
|
||||
|
||||
for (int i = 0; i < mLyricInfo.songLines.size(); i++) {
|
||||
|
||||
StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint,
|
||||
(int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH),
|
||||
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
||||
|
||||
if (staticLayout.getLineCount() > 1) {
|
||||
mEnableLineFeed = true;
|
||||
mExtraHeight = mExtraHeight + (staticLayout.getLineCount() - 1) * mTextHeight;
|
||||
}
|
||||
|
||||
mLineFeedRecord.add(i, mExtraHeight);
|
||||
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
invalidateView();
|
||||
}
|
||||
}
|
||||
|
||||
private void setLineSpace(float lineSpace) {
|
||||
if (mLineSpace != lineSpace) {
|
||||
mLineSpace = getRawSize(TypedValue.COMPLEX_UNIT_DIP, lineSpace);
|
||||
measureLineHeight();
|
||||
mScrollY = measureCurrentScrollY(mCurrentPlayLine);
|
||||
invalidateView();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
resetView();
|
||||
}
|
||||
|
||||
private void actionCancel(MotionEvent event) {
|
||||
releaseVelocityTracker();
|
||||
}
|
||||
|
||||
private void actionDown(MotionEvent event) {
|
||||
removeCallbacks(hideIndicator);
|
||||
mLastScrollY = mScrollY;
|
||||
mDownX = event.getX();
|
||||
mDownY = event.getY();
|
||||
if (mFlingAnimator != null) {
|
||||
mFlingAnimator.cancel();
|
||||
mFlingAnimator = null;
|
||||
}
|
||||
setUserTouch(true);
|
||||
}
|
||||
|
||||
private boolean overScrolled() {
|
||||
|
||||
return scrollable() && (mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0) || mScrollY < 0);
|
||||
}
|
||||
|
||||
private void actionMove(MotionEvent event) {
|
||||
if (scrollable()) {
|
||||
final VelocityTracker tracker = mVelocityTracker;
|
||||
tracker.computeCurrentVelocity(UNITS_SECOND, maxVelocity);
|
||||
mScrollY = mLastScrollY + mDownY - event.getY();
|
||||
mVelocity = tracker.getYVelocity();
|
||||
measureCurrentLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void actionUp(MotionEvent event) {
|
||||
|
||||
postDelayed(hideIndicator, 3 * UNITS_SECOND);
|
||||
|
||||
releaseVelocityTracker();
|
||||
|
||||
if (scrollable()) {
|
||||
if (overScrolled() && mScrollY < 0) {
|
||||
smoothScrollTo(0);
|
||||
return;
|
||||
}
|
||||
if (overScrolled() && mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)) {
|
||||
smoothScrollTo(mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0));
|
||||
return;
|
||||
}
|
||||
if (Math.abs(mVelocity) > THRESHOLD_Y_VELOCITY) {
|
||||
doFlingAnimator(mVelocity);
|
||||
return;
|
||||
}
|
||||
if (mShowIndicator && clickPlayer(event)) {
|
||||
if (mLineNumberUnderIndicator != mCurrentPlayLine) {
|
||||
mShowIndicator = false;
|
||||
if (mClickListener != null) {
|
||||
setUserTouch(false);
|
||||
mClickListener.onPlayerClicked(mLyricInfo.songLines.get(mLineNumberUnderIndicator).start, mLyricInfo.songLines.get(mLineNumberUnderIndicator).content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String measureCurrentTime() {
|
||||
DecimalFormat format = new DecimalFormat("00");
|
||||
if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 < mLineCount && mLineNumberUnderIndicator > 0) {
|
||||
return format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 % 60);
|
||||
}
|
||||
if (mLyricInfo != null && mLineCount > 0 && (mLineNumberUnderIndicator - 1) >= mLineCount) {
|
||||
return format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 % 60);
|
||||
}
|
||||
if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 <= 0) {
|
||||
return format.format(mLyricInfo.songLines.get(0).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(0).start / 1000 % 60);
|
||||
}
|
||||
return mDefaultTime;
|
||||
}
|
||||
|
||||
private void drawIndicator(Canvas canvas) {
|
||||
|
||||
//绘制 播放 按钮
|
||||
Path pathPlay = new Path();
|
||||
float yCoordinate = mBtnPlayRect.left + (float) Math.sqrt(Math.pow(mBtnPlayRect.width(), 2) - Math.pow(mBtnPlayRect.width() * 0.5f, 2));
|
||||
float remainWidth = mBtnPlayRect.right - yCoordinate;
|
||||
|
||||
pathPlay.moveTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f);
|
||||
pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() + mBtnPlayRect.height() * 0.5f);
|
||||
pathPlay.lineTo(yCoordinate, mBtnPlayRect.centerY());
|
||||
pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f);
|
||||
|
||||
canvas.drawPath(pathPlay, mBtnPlayPaint);
|
||||
|
||||
//绘制 分割线
|
||||
Path pathLine = new Path();
|
||||
pathLine.moveTo(mBtnPlayRect.right + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN) - remainWidth, getMeasuredHeight() * 0.5f);
|
||||
pathLine.lineTo(getWidth() - mTimerRect.width() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT) - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN), getHeight() * 0.5f);
|
||||
canvas.drawPath(pathLine, mLinePaint);
|
||||
|
||||
//绘制 时间
|
||||
canvas.drawText(measureCurrentTime(), getWidth() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT), (getHeight() + mTimerRect.height()) * 0.5f, mTimerPaint);
|
||||
}
|
||||
|
||||
private boolean clickPlayer(MotionEvent event) {
|
||||
if (mBtnPlayRect != null && mDownX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT)) {
|
||||
float upX = event.getX();
|
||||
float upY = event.getY();
|
||||
return upX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doFlingAnimator(float velocity) {
|
||||
|
||||
float distance = (velocity / Math.abs(velocity) * (Math.abs(velocity) * SLIDE_COEFFICIENT));
|
||||
float to = Math.min(Math.max(0, (mScrollY - distance)), (mLineCount - 1) * mLineHeight + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0));
|
||||
|
||||
mFlingAnimator = ValueAnimator.ofFloat(mScrollY, to);
|
||||
mFlingAnimator.addUpdateListener(animation -> {
|
||||
mScrollY = (float) animation.getAnimatedValue();
|
||||
measureCurrentLine();
|
||||
invalidateView();
|
||||
});
|
||||
|
||||
mFlingAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
mVelocity = 0;
|
||||
mFling = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mFling = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
super.onAnimationCancel(animation);
|
||||
}
|
||||
});
|
||||
|
||||
mFlingAnimator.setDuration(FLING_ANIMATOR_DURATION);
|
||||
mFlingAnimator.setInterpolator(new DecelerateInterpolator());
|
||||
mFlingAnimator.start();
|
||||
}
|
||||
|
||||
private void setUserTouch(boolean isUserTouch) {
|
||||
if (isUserTouch) {
|
||||
mUserTouch = true;
|
||||
mShowIndicator = true;
|
||||
} else {
|
||||
mUserTouch = false;
|
||||
mShowIndicator = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseVelocityTracker() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initMyView(Context context) {
|
||||
maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
|
||||
initPaint();
|
||||
initAllBounds();
|
||||
}
|
||||
|
||||
private void initAllBounds() {
|
||||
setRawTextSize(mTextSize);
|
||||
|
||||
setLineSpace(mLineSpace);
|
||||
measureLineHeight();
|
||||
|
||||
mTimerRect = new Rect();
|
||||
mTimerPaint.getTextBounds(mDefaultTime, 0, mDefaultTime.length(), mTimerRect);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
mTextPaint = new TextPaint();
|
||||
mTextPaint.setDither(true);
|
||||
mTextPaint.setAntiAlias(true);
|
||||
Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/circular_std_book.otf");
|
||||
mTextPaint.setTypeface(typeface);
|
||||
|
||||
switch (mTextAlign) {
|
||||
case LEFT:
|
||||
mTextPaint.setTextAlign(Paint.Align.LEFT);
|
||||
break;
|
||||
case CENTER:
|
||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
break;
|
||||
case RIGHT:
|
||||
mTextPaint.setTextAlign(Paint.Align.RIGHT);
|
||||
break;
|
||||
}
|
||||
|
||||
mBtnPlayPaint = new Paint();
|
||||
mBtnPlayPaint.setDither(true);
|
||||
mBtnPlayPaint.setAntiAlias(true);
|
||||
mBtnPlayPaint.setColor(mBtnColor);
|
||||
mBtnPlayPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
mBtnPlayPaint.setAlpha(128);
|
||||
|
||||
mLinePaint = new Paint();
|
||||
mLinePaint.setDither(true);
|
||||
mLinePaint.setAntiAlias(true);
|
||||
mLinePaint.setColor(mLineColor);
|
||||
mLinePaint.setAlpha(64);
|
||||
mLinePaint.setStrokeWidth(1.0f);
|
||||
mLinePaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mTimerPaint = new Paint();
|
||||
mTimerPaint.setDither(true);
|
||||
mTimerPaint.setAntiAlias(true);
|
||||
mTimerPaint.setColor(Color.WHITE);
|
||||
mTimerPaint.setTextAlign(Paint.Align.RIGHT);
|
||||
mTimerPaint.setTextSize(getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_TIME_TEXT_SIZE));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private float measureCurrentScrollY(int line) {
|
||||
if (mEnableLineFeed && line > 1) {
|
||||
return (line - 1) * mLineHeight + mLineFeedRecord.get(line - 1);
|
||||
}
|
||||
return (line - 1) * mLineHeight;
|
||||
}
|
||||
|
||||
private void invalidateView() {
|
||||
if (Looper.getMainLooper() == Looper.myLooper()) {
|
||||
// 当前线程是主UI线程,直接刷新。
|
||||
invalidate();
|
||||
} else {
|
||||
// 当前线程是非UI线程,post刷新。
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void measureLineHeight() {
|
||||
Rect lineBound = new Rect();
|
||||
mTextPaint.getTextBounds(mDefaultHint, 0, mDefaultHint.length(), lineBound);
|
||||
mTextHeight = lineBound.height();
|
||||
mLineHeight = mTextHeight + mLineSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* To measure current showing line number based on the view's scroll Y
|
||||
*/
|
||||
private void measureCurrentLine() {
|
||||
float baseScrollY = mScrollY + mLineHeight * 0.5f;
|
||||
|
||||
if (mEnableLineFeed) {
|
||||
for (int i = mLyricInfo.songLines.size(); i >= 0; i--) {
|
||||
if (baseScrollY > measureCurrentScrollY(i) + mLineSpace * 0.2) {
|
||||
mLineNumberUnderIndicator = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mLineNumberUnderIndicator = (int) (baseScrollY / mLineHeight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void smoothScrollTo(float toY) {
|
||||
final ValueAnimator animator = ValueAnimator.ofFloat(mScrollY, toY);
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
mScrollY = (Float) valueAnimator.getAnimatedValue();
|
||||
invalidateView();
|
||||
});
|
||||
|
||||
animator.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
mFling = false;
|
||||
measureCurrentLine();
|
||||
invalidateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
mFling = true;
|
||||
}
|
||||
});
|
||||
animator.setDuration(640);
|
||||
animator.setInterpolator(new LinearInterpolator());
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private boolean scrollable() {
|
||||
return mLyricInfo != null && mLyricInfo.songLines != null && mLyricInfo.songLines.size() > 0;
|
||||
}
|
||||
|
||||
private void scrollToCurrentTimeMillis(long time) {
|
||||
|
||||
int position = 0;
|
||||
if (scrollable()) {
|
||||
for (int i = 0, size = mLineCount; i < size; i++) {
|
||||
LineInfo lineInfo = mLyricInfo.songLines.get(i);
|
||||
if (lineInfo != null && lineInfo.start >= time) {
|
||||
position = i;
|
||||
break;
|
||||
}
|
||||
if (i == mLineCount - 1) {
|
||||
position = mLineCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mCurrentPlayLine != position) {
|
||||
mCurrentPlayLine = position;
|
||||
if (!mFling && !mUserTouch) {
|
||||
smoothScrollTo(measureCurrentScrollY(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupLyricResource(InputStream inputStream, String charsetName) {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
LyricInfo lyricInfo = new LyricInfo();
|
||||
lyricInfo.songLines = new ArrayList<>();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
|
||||
BufferedReader reader = new BufferedReader(inputStreamReader);
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
analyzeLyric(lyricInfo, line);
|
||||
}
|
||||
reader.close();
|
||||
inputStream.close();
|
||||
inputStreamReader.close();
|
||||
|
||||
mLyricInfo = lyricInfo;
|
||||
mLineCount = mLyricInfo.songLines.size();
|
||||
invalidateView();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
invalidateView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 逐行解析歌词内容
|
||||
*/
|
||||
private void analyzeLyric(LyricInfo lyricInfo, String line) {
|
||||
int index = line.lastIndexOf("]");
|
||||
if (line.startsWith("[offset:")) {
|
||||
// time offset
|
||||
lyricInfo.songOffset = Long.parseLong(line.substring(8, index).trim());
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("[ti:")) {
|
||||
// title
|
||||
lyricInfo.songTitle = line.substring(4, index).trim();
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("[ar:")) {
|
||||
// artist
|
||||
lyricInfo.songArtist = line.substring(4, index).trim();
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("[al:")) {
|
||||
// album
|
||||
lyricInfo.songAlbum = line.substring(4, index).trim();
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("[by:")) {
|
||||
return;
|
||||
}
|
||||
if (index >= 9 && line.trim().length() > index + 1) {
|
||||
// lyrics
|
||||
LineInfo lineInfo = new LineInfo();
|
||||
lineInfo.content = line.substring(10, line.length());
|
||||
lineInfo.start = measureStartTimeMillis(line.substring(0, index));
|
||||
lyricInfo.songLines.add(lineInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串中获得时间值
|
||||
*/
|
||||
private long measureStartTimeMillis(String str) {
|
||||
long minute = Long.parseLong(str.substring(1, 3));
|
||||
long second = Long.parseLong(str.substring(4, 6));
|
||||
long millisecond = Long.parseLong(str.substring(7, 9));
|
||||
return millisecond + second * 1000 + minute * 60 * 1000;
|
||||
}
|
||||
|
||||
private void resetLyricInfo() {
|
||||
if (mLyricInfo != null) {
|
||||
if (mLyricInfo.songLines != null) {
|
||||
mLyricInfo.songLines.clear();
|
||||
mLyricInfo.songLines = null;
|
||||
}
|
||||
mLyricInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetView() {
|
||||
mCurrentPlayLine = 0;
|
||||
resetLyricInfo();
|
||||
invalidateView();
|
||||
mLineCount = 0;
|
||||
mScrollY = 0;
|
||||
mEnableLineFeed = false;
|
||||
mLineFeedRecord.clear();
|
||||
mExtraHeight = 0;
|
||||
}
|
||||
|
||||
private float getRawSize(int unit, float size) {
|
||||
Context context = getContext();
|
||||
Resources resources;
|
||||
if (context == null) {
|
||||
resources = Resources.getSystem();
|
||||
} else {
|
||||
resources = context.getResources();
|
||||
}
|
||||
return TypedValue.applyDimension(unit, size, resources.getDisplayMetrics());
|
||||
}
|
||||
|
||||
private void setRawTextSize(float size) {
|
||||
if (size != mTextPaint.getTextSize()) {
|
||||
mTextPaint.setTextSize(size);
|
||||
measureLineHeight();
|
||||
mScrollY = measureCurrentScrollY(mCurrentPlayLine);
|
||||
invalidateView();
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef({LEFT, CENTER, RIGHT})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Alignment {
|
||||
}
|
||||
|
||||
public interface OnPlayerClickListener {
|
||||
void onPlayerClicked(long progress, String content);
|
||||
}
|
||||
|
||||
private class LyricInfo {
|
||||
List<LineInfo> songLines;
|
||||
|
||||
String songArtist;
|
||||
String songTitle;
|
||||
String songAlbum;
|
||||
|
||||
long songOffset;
|
||||
}
|
||||
|
||||
private class LineInfo {
|
||||
String content;
|
||||
long start;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2017. Alexander Bilchuk <a.bilchuk@sandrlab.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.PagerSnapHelper;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SnapHelper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder;
|
||||
|
||||
public class MetalRecyclerViewPager extends RecyclerView {
|
||||
|
||||
private int itemMargin;
|
||||
|
||||
public MetalRecyclerViewPager(Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public MetalRecyclerViewPager(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public MetalRecyclerViewPager(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MetalRecyclerViewPager, 0, 0);
|
||||
itemMargin = (int) typedArray.getDimension(R.styleable.MetalRecyclerViewPager_itemMargin, 0f);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
|
||||
SnapHelper snapHelper = new PagerSnapHelper();
|
||||
snapHelper.attachToRecyclerView(this);
|
||||
}
|
||||
|
||||
public void setAdapter(Adapter adapter) {
|
||||
if (MetalAdapter.class.isInstance(adapter)) {
|
||||
MetalAdapter metalAdapter = (MetalAdapter) adapter;
|
||||
metalAdapter.setItemMargin(itemMargin);
|
||||
metalAdapter.updateDisplayMetrics();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only MetalAdapter is allowed here");
|
||||
}
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
public static abstract class MetalAdapter<VH extends MetalViewHolder> extends RecyclerView.Adapter<VH> {
|
||||
|
||||
private DisplayMetrics metrics;
|
||||
private int itemMargin;
|
||||
private int itemWidth;
|
||||
|
||||
public MetalAdapter(@NonNull DisplayMetrics metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
void setItemMargin(int itemMargin) {
|
||||
this.itemMargin = itemMargin;
|
||||
}
|
||||
|
||||
void updateDisplayMetrics() {
|
||||
itemWidth = metrics.widthPixels - itemMargin * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
int currentItemWidth = itemWidth;
|
||||
|
||||
if (position == 0) {
|
||||
currentItemWidth += itemMargin;
|
||||
holder.rootLayout.setPadding(0, 0, 0, 0);
|
||||
} else if (position == getItemCount() - 1) {
|
||||
currentItemWidth += itemMargin;
|
||||
holder.rootLayout.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int height = holder.rootLayout.getLayoutParams().height;
|
||||
holder.rootLayout.setLayoutParams(new ViewGroup.LayoutParams(currentItemWidth, height));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static abstract class MetalViewHolder extends MediaEntryViewHolder {
|
||||
|
||||
ViewGroup rootLayout;
|
||||
|
||||
public MetalViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
rootLayout = (ViewGroup) itemView.findViewById(R.id.root_layout);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
*/
|
||||
public class NetworkImageView extends CircularImageView {
|
||||
|
||||
public NetworkImageView(Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public NetworkImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public NetworkImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
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);
|
||||
Glide.with(context).load(url).asBitmap()
|
||||
.error(R.drawable.ic_person_flat)
|
||||
.placeholder(R.drawable.ic_person_flat)
|
||||
.into(this);
|
||||
attributes.recycle();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.CornerPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Property;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
|
||||
public class PlayPauseDrawable extends Drawable {
|
||||
private static final long PLAY_PAUSE_ANIMATION_DURATION = 250;
|
||||
|
||||
private static final Property<PlayPauseDrawable, Float> PROGRESS =
|
||||
new Property<PlayPauseDrawable, Float>(Float.class, "progress") {
|
||||
@Override
|
||||
public Float get(@NonNull PlayPauseDrawable d) {
|
||||
return d.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(@NonNull PlayPauseDrawable d, Float value) {
|
||||
d.setProgress(value);
|
||||
}
|
||||
};
|
||||
|
||||
private final Path leftPauseBar = new Path();
|
||||
private final Path rightPauseBar = new Path();
|
||||
private final Paint paint = new Paint();
|
||||
private final float pauseBarWidth;
|
||||
private final float pauseBarHeight;
|
||||
private final float pauseBarDistance;
|
||||
|
||||
private float width;
|
||||
private float height;
|
||||
|
||||
private float progress;
|
||||
private boolean isPlay;
|
||||
private boolean isPlaySet;
|
||||
|
||||
private Animator animator;
|
||||
|
||||
public PlayPauseDrawable(@NonNull Context context) {
|
||||
final Resources res = context.getResources();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(Color.WHITE);
|
||||
|
||||
pauseBarWidth = res.getDimensionPixelSize(R.dimen.pause_bar_width);
|
||||
pauseBarHeight = res.getDimensionPixelSize(R.dimen.pause_bar_height);
|
||||
pauseBarDistance = res.getDimensionPixelSize(R.dimen.pause_bar_distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear interpolate between a and b with parameter t.
|
||||
*/
|
||||
private static float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull final Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
if (bounds.width() > 0 && bounds.height() > 0) {
|
||||
width = bounds.width();
|
||||
height = bounds.height();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
leftPauseBar.rewind();
|
||||
rightPauseBar.rewind();
|
||||
|
||||
// The current distance between the two pause bars.
|
||||
final float barDist = lerp(pauseBarDistance, 0f, progress);
|
||||
// The current width of each pause bar.
|
||||
float rawBarWidth = lerp(pauseBarWidth, pauseBarHeight / 1.75f, progress);
|
||||
// We have to round the bar width when finishing the progress to prevent the gap
|
||||
// that might occur onDraw because of a pixel is lost when casting float to int instead of rounding it.
|
||||
final float barWidth = progress == 1f ? Math.round(rawBarWidth) : rawBarWidth;
|
||||
// The current position of the left pause bar's top left coordinate.
|
||||
final float firstBarTopLeft = lerp(0f, barWidth, progress);
|
||||
// The current position of the right pause bar's top right coordinate.
|
||||
final float secondBarTopRight = lerp(2f * barWidth + barDist, barWidth + barDist, progress);
|
||||
|
||||
// Draw the left pause bar. The left pause bar transforms into the
|
||||
// top half of the play button triangle by animating the position of the
|
||||
// rectangle's top left coordinate and expanding its bottom width.
|
||||
leftPauseBar.moveTo(0f, 0f);
|
||||
leftPauseBar.lineTo(firstBarTopLeft, -pauseBarHeight);
|
||||
leftPauseBar.lineTo(barWidth, -pauseBarHeight);
|
||||
leftPauseBar.lineTo(barWidth, 0f);
|
||||
leftPauseBar.close();
|
||||
|
||||
// Draw the right pause bar. The right pause bar transforms into the
|
||||
// bottom half of the play button triangle by animating the position of the
|
||||
// rectangle's top right coordinate and expanding its bottom width.
|
||||
rightPauseBar.moveTo(barWidth + barDist, 0f);
|
||||
rightPauseBar.lineTo(barWidth + barDist, -pauseBarHeight);
|
||||
rightPauseBar.lineTo(secondBarTopRight, -pauseBarHeight);
|
||||
rightPauseBar.lineTo(2 * barWidth + barDist, 0f);
|
||||
rightPauseBar.close();
|
||||
|
||||
final int saveCount = canvas.save();
|
||||
|
||||
// Translate the play button a tiny bit to the right so it looks more centered.
|
||||
canvas.translate(lerp(0f, pauseBarHeight / 8f, progress), 0f);
|
||||
|
||||
// (1) Pause --> Play: rotate 0 to 90 degrees clockwise.
|
||||
// (2) Play --> Pause: rotate 90 to 180 degrees clockwise.
|
||||
final float rotationProgress = isPlay ? 1f - progress : progress;
|
||||
final float startingRotation = isPlay ? 90f : 0f;
|
||||
canvas.rotate(lerp(startingRotation, startingRotation + 90f, rotationProgress), width / 2f, height / 2f);
|
||||
|
||||
// Position the pause/play button in the center of the drawable's bounds.
|
||||
canvas.translate(Math.round(width / 2f - ((2f * barWidth + barDist) / 2f)), Math.round(height / 2f + (pauseBarHeight / 2f)));
|
||||
|
||||
// Draw the two bars that form the animated pause/play button.
|
||||
canvas.drawPath(leftPauseBar, paint);
|
||||
canvas.drawPath(rightPauseBar, paint);
|
||||
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Animator getPausePlayAnimator() {
|
||||
isPlaySet = !isPlaySet;
|
||||
final Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, isPlay ? 1f : 0f, isPlay ? 0f : 1f);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
isPlay = !isPlay;
|
||||
}
|
||||
});
|
||||
return anim;
|
||||
}
|
||||
|
||||
private float getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
private void setProgress(float progress) {
|
||||
this.progress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
paint.setColorFilter(cf);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
public void setPlay(boolean animate) {
|
||||
if (animate) {
|
||||
if (!isPlaySet) {
|
||||
togglePlayPause();
|
||||
}
|
||||
} else {
|
||||
isPlaySet = true;
|
||||
isPlay = true;
|
||||
setProgress(1f);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPause(boolean animate) {
|
||||
if (animate) {
|
||||
if (isPlaySet) {
|
||||
togglePlayPause();
|
||||
}
|
||||
} else {
|
||||
isPlaySet = false;
|
||||
isPlay = false;
|
||||
setProgress(0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void togglePlayPause() {
|
||||
if (animator != null) {
|
||||
animator.cancel();
|
||||
}
|
||||
|
||||
animator = getPausePlayAnimator();
|
||||
animator.setInterpolator(new DecelerateInterpolator());
|
||||
animator.setDuration(PLAY_PAUSE_ANIMATION_DURATION);
|
||||
animator.start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* Frame layout that has rounded corners (it clips content too).
|
||||
*
|
||||
* @author Anton Chekulaev
|
||||
*/
|
||||
public class RoundCornerFrameLayout extends FrameLayout {
|
||||
|
||||
private final Path stencilPath = new Path();
|
||||
private float cornerRadius = 0;
|
||||
|
||||
public RoundCornerFrameLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RoundCornerFrameLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundCornerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
TypedArray attrArray = context
|
||||
.obtainStyledAttributes(attrs, R.styleable.RoundCornerFrameLayout, 0, 0);
|
||||
try {
|
||||
cornerRadius = attrArray.getDimension(R.styleable.RoundCornerFrameLayout_corner_radius, 0f);
|
||||
} finally {
|
||||
attrArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/*@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
// compute the path
|
||||
stencilPath.reset();
|
||||
stencilPath.addRoundRect(0, 0, w, h, cornerRadius, cornerRadius, Path.Direction.CW);
|
||||
stencilPath.close();
|
||||
|
||||
}
|
||||
*/
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
final int count = canvas.save();
|
||||
final Path path = new Path();
|
||||
final RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
final float[] arrayRadius = {cornerRadius, cornerRadius, cornerRadius, cornerRadius,
|
||||
cornerRadius, cornerRadius, cornerRadius, cornerRadius};
|
||||
|
||||
path.addRoundRect(rect, arrayRadius, Path.Direction.CW);
|
||||
canvas.clipPath(path, Region.Op.REPLACE);
|
||||
canvas.clipPath(path);
|
||||
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.BottomSheetDialog;
|
||||
import android.support.design.widget.BottomSheetDialogFragment;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
|
||||
/**
|
||||
* Created by yu on 2016/11/10.
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
public class RoundedBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
@Override
|
||||
public int getTheme() {
|
||||
//noinspection ConstantConditions
|
||||
return PreferenceUtil.getInstance(getContext()).getGeneralTheme() == R.style.Theme_RetroMusic_Light ? R.style.BottomSheetDialogTheme : R.style.BottomSheetDialogThemeDark;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
//noinspection ConstantConditions
|
||||
return new BottomSheetDialog(getContext(), getTheme());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.design.widget.CollapsingToolbarLayout;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.TypefaceHelper;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
*/
|
||||
|
||||
public class SansFontCollapsingToolbarLayout extends CollapsingToolbarLayout {
|
||||
public SansFontCollapsingToolbarLayout(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public SansFontCollapsingToolbarLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public SansFontCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
Typeface typefaceBold = TypefaceHelper.get(context, getResources().getString(R.string.sans_bold));
|
||||
setExpandedTitleTypeface(typefaceBold);
|
||||
setCollapsedTitleTypeface(typefaceBold);
|
||||
|
||||
}
|
||||
|
||||
public void setTitle(int i) {
|
||||
setTitle(getContext().getString(i));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
|
||||
public class VerticalTextView extends android.support.v7.widget.AppCompatTextView {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean setFrame(int l, int t, int r, int b) {
|
||||
return super.setFrame(l, t, l + (b - t), t + (r - l));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (topDown) {
|
||||
canvas.translate(getHeight(), 0);
|
||||
canvas.rotate(90);
|
||||
} else {
|
||||
canvas.translate(0, getWidth());
|
||||
canvas.rotate(-90);
|
||||
}
|
||||
canvas.clipRect(0, 0, getWidth(), getHeight(), android.graphics.Region.Op.REPLACE);
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package code.name.monkey.retromusic.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class WidthFitSquareLayout extends FrameLayout {
|
||||
private boolean forceSquare = true;
|
||||
|
||||
public WidthFitSquareLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public WidthFitSquareLayout(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
public WidthFitSquareLayout(Context context, AttributeSet attributeSet, int i) {
|
||||
super(context, attributeSet, i);
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
public WidthFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) {
|
||||
super(context, attributeSet, i, i2);
|
||||
}
|
||||
|
||||
public void forceSquare(boolean z) {
|
||||
this.forceSquare = z;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
protected void onMeasure(int i, int i2) {
|
||||
if (this.forceSquare) {
|
||||
i2 = i;
|
||||
}
|
||||
super.onMeasure(i, i2);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue