onSaveInstanceState()
method of our activation, pull the value of the scroll from the component and save, and later, when you re-create, set the scroll to our component. And it will work, but it can hardly be called the right approach. Imagine that we have not one parameter to be saved, but ten, or not one component, but ten ... with ten parameters.
onSaveInstanceState()
class and onRestoreInstanceState(Parcelable state)
. However, there is a small difference from the analogues in the activation. There we deal with the Bundle
, here we have Parcelable. We need to make our own Parcelable class, which must be derived from android.view.View.BaseSavedState
.
public static class SavedState extends BaseSavedState { int xOffset; int instrumentWidth; // :) SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(xOffset); out.writeInt(instrumentWidth); } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; private SavedState(Parcel in) { super(in); xOffset = in.readInt(); instrumentWidth = in.readInt(); } }
@Override protected Parcelable onSaveInstanceState() { SavedState st = new SavedState(super.onSaveInstanceState()); st.xOffset = xOffset; st.instrumentWidth = xOffset; return st; } protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); xOffset = ss.xOffset; xOffset = ss.instrumentWidth; };
if (measurementChanged) { measurementChanged = false; keyboard.initializeInstrument(getMeasuredHeight(), getContext()); float oldInstrumentWidth = instrumentWidth; instrumentWidth = keyboard.getWidth(); float ratio = (float) instrumentWidth / oldInstrumentWidth; // xOffset = (int) (xOffset * ratio); }
private EdgeEffectCompat leftEdgeEffect; private EdgeEffectCompat rightEdgeEffect;
public void draw(Canvas canvas) { super.draw(canvas); boolean needsInvalidate = false; final int overScrollMode = ViewCompat.getOverScrollMode(this); if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS)) { if (!leftEdgeEffect.isFinished()) { final int restoreCount = canvas.save(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); final int width = getWidth(); canvas.rotate(270); canvas.translate(-height + getPaddingTop(), 0); leftEdgeEffect.setSize(height, width); needsInvalidate |= leftEdgeEffect.draw(canvas); canvas.restoreToCount(restoreCount); } if (!rightEdgeEffect.isFinished()) { final int restoreCount = canvas.save(); final int width = getWidth(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.rotate(90); canvas.translate(-getPaddingTop(), -width); rightEdgeEffect.setSize(height, width); needsInvalidate |= rightEdgeEffect.draw(canvas); canvas.restoreToCount(restoreCount); } } else { leftEdgeEffect.finish(); rightEdgeEffect.finish(); } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } }
if (!leftEdgeEffect.isFinished()) { final int restoreCount = canvas.save(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); final int width = getWidth(); canvas.rotate(270); canvas.translate(-height + getPaddingTop(), 0); leftEdgeEffect.setSize(height, width); needsInvalidate |= leftEdgeEffect.draw(canvas); canvas.restoreToCount(restoreCount); }
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { resetTouchFeedback(); xOffset += distanceX; if (xOffset < 0) { leftEdgeEffect.onPull(distanceX / (float) getMeasuredWidth()); } if (xOffset > instrumentWidth - getMeasuredWidth()) { rightEdgeEffect.onPull(distanceX / (float) getMeasuredWidth()); } if (!awakenScrollBars()) { invalidate(); } return true; }
onDraw
:
if (scroller.isOverScrolled()) { if (xOffset < 0) { leftEdgeEffect.onAbsorb(getCurrentVelocity()); } else { rightEdgeEffect.onAbsorb(getCurrentVelocity()); } } // ... @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private int getCurrentVelocity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { return (int) scroller.getCurrVelocity(); } return 0; }
Scroller.getCurrVelocity()
method is available to us only starting from ICS, so I marked the method as aimed at API 14+. Yes, this is far from ideal, but, again, this is what we have.
private ArrayList<Note> notesToDraw = new ArrayList<Note>();
public void drawOverlays(ArrayList<Note> notes, Canvas canvas) { int firstVisibleKey = getFirstVisibleKey(); int lastVisibleKey = getLastVisibleKey(); for (Note note : notes) { int midiCode = note.getMidiCode(); if (midiCode >= firstVisibleKey && midiCode <= lastVisibleKey) { drawNoteFromMidi(canvas, note, midiCode, false); } } } private void drawNoteFromMidi(Canvas canvas, Note note, int midiCode, boolean replica) { Key key = keysArray[midiCode - Keyboard.START_MIDI_CODE]; overlayTextPaint.setColor(circleColor); canvas.drawCircle(key.getOverlayPivotX(), key.getOverlayPivotY(), overlayCircleRadius, overlayTextPaint); String name = note.toString(); overlayTextPaint.getTextBounds(name, 0, name.length(), bounds); int width = bounds.right - bounds.left; int height = bounds.bottom - bounds.top; overlayTextPaint.setColor(Color.BLACK); canvas.drawText(name, key.getOverlayPivotX() - width / 2, key.getOverlayPivotY() + height / 2, overlayTextPaint); }
<declare-styleable name="PianoView"> <attr name="overlay_color" format="color"></attr> <attr name="overlay_circle_radius" format="dimension"></attr> <attr name="overlay_circle_text_size" format="dimension"></attr> </declare-styleable>
TypedArray pianoAttrs = context.obtainStyledAttributes(attrs, R.styleable.PianoView); int circleColor; float circleRadius; float circleTextSize; try { circleColor = pianoAttrs.getColor(R.styleable.PianoView_overlay_color, Color.GREEN); circleRadius = pianoAttrs.getDimension(R.styleable.PianoView_overlay_circle_radius, TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, context.getResources().getDisplayMetrics())); circleTextSize = pianoAttrs.getDimension(R.styleable.PianoView_overlay_circle_text_size, TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, context.getResources().getDisplayMetrics())); } finally { pianoAttrs.recycle(); }
xmlns:piano="http://schemas.android.com/apk/res-auto"
, after which we get the following markup file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:piano="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".PianoDemoActivity" > <com.evilduck.piano.views.instrument.PianoView android:id="@+id/instrument_view" android:layout_width="match_parent" android:layout_height="300dip" piano:overlay_circle_radius="18dip" piano:overlay_circle_text_size="18sp" piano:overlay_color="#00FF00" /> </RelativeLayout>
private OnScaleGestureListener scaleGestureListener = new OnScaleGestureListener() { @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public boolean onScale(ScaleGestureDetector detector) { scaleX *= detector.getScaleFactor(); if (scaleX < 1) { scaleX = 1; } if (scaleX > 2) { scaleX = 2; } ViewCompat.postInvalidateOnAnimation(PianoView.this); return true; } };
canvas.save(); // canvas.scale(scaleX, 1.0f); canvas.translate(-localXOffset, 0); keyboard.updateBounds(localXOffset, canvasWidth + localXOffset); keyboard.draw(canvas); if (!notesToDraw.isEmpty()) { keyboard.drawOverlays(notesToDraw, canvas); } canvas.restore();
Source: https://habr.com/ru/post/176919/