From dd2992a3b3de8ae00b08011424a46b32d7020dc1 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Fri, 1 Jul 2016 18:41:29 -0400 Subject: [PATCH] More refactoring Plumb a GridViewPager into being. Lots of wrestling with drawing gunk. --- .../acmetensortoys/watchviz/MainActivity.java | 123 +++++++------ .../watchviz/MainGridViewAdapter.java | 166 ++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 115 +++++++----- .../watchviz/vizlib/AudioCanvas.java | 162 ++++++++++------- .../watchviz/vizlib/AudioProvider.java | 50 ++++-- .../watchviz/vizlib/Rendering.java | 2 +- .../watchviz/vizlib/RenderingSelector.java | 69 -------- 7 files changed, 431 insertions(+), 256 deletions(-) create mode 100644 app/src/main/java/com/acmetensortoys/watchviz/MainGridViewAdapter.java delete mode 100644 vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java diff --git a/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java b/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java index 914c6b4..dd7cc5a 100644 --- a/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java +++ b/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java @@ -16,88 +16,76 @@ along with this program. If not, see . */ +/* + This is the glue code that provides the Activity that Android interacts with. + Most of the actual UI work of interest is in MainGridViewAdapter. + */ + package com.acmetensortoys.watchviz; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.wearable.activity.WearableActivity; -import android.support.wearable.view.BoxInsetLayout; +import android.support.wearable.view.GridViewPager; import android.util.Log; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import com.acmetensortoys.watchviz.vizlib.AudioCanvas; import com.acmetensortoys.watchviz.vizlib.AudioProvider; -import com.acmetensortoys.watchviz.vizlib.RenderingSelector; public class MainActivity extends WearableActivity { private static final SimpleDateFormat AMBIENT_DATE_FORMAT = new SimpleDateFormat("HH:mm", Locale.US); + private static final int TIME_UPDATE_NOT_AMBIENT_PERIOD = 2000; - private BoxInsetLayout mOuterContainer; - private LinearLayout mInnerContainer; + private Handler mHandler; + private RelativeLayout mTextContainer; private TextView mTextView, mClockView, mDebugView; - private SurfaceView mACSurfaceView; - private AudioCanvas mAudioCanvas; private AudioProvider mAudioProvider = new AudioProvider(); - private void createSurface() { - Log.d("createSurface", "top"); - mACSurfaceView = new SurfaceView(this); - mAudioCanvas = new AudioCanvas(mACSurfaceView, mAudioProvider); + private GridViewPager mGridView; - final RenderingSelector rs = new RenderingSelector(mDebugView, mAudioCanvas); - - mTextView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - rs.prevCyclerCB(); - } - }); - mACSurfaceView.setOnClickListener(new View.OnClickListener(){ - @Override - public void onClick(View v) { mAudioCanvas.onClick(); } - }); - mACSurfaceView.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - rs.nextCyclerCB(); - return true; - } - }); - mInnerContainer.addView(mACSurfaceView, -1, - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); + private void createSurfaces() { + Log.d("createSurfaces", "top"); + MainGridViewAdapter mgva = new MainGridViewAdapter(this, mAudioProvider, mDebugView); + mGridView.setOnPageChangeListener(mgva); + mGridView.setAdapter(mgva); + // Fake the selection callback. Why doesn't this happen automatically? :( + mgva.onPageSelected(mGridView.getCurrentItem().x, mGridView.getCurrentItem().y); } - private void removeSurface() { - Log.d("removeSurface", "removing view"); - mInnerContainer.removeView(mACSurfaceView); - Log.d("removeSurface", "done"); + private void removeSurfaces() { + Log.d("removeSurfaces", "removing grid view"); + mGridView.setAdapter(null); + Log.d("removeSurfaces", "done"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // grab an interface to our local looper + mHandler = new Handler(); + setContentView(R.layout.activity_main); setAmbientEnabled(); - mOuterContainer = (BoxInsetLayout) findViewById(R.id.top_container); - mInnerContainer = (LinearLayout) findViewById(R.id.linear_container); + mTextContainer = (RelativeLayout) findViewById(R.id.text_container); mTextView = (TextView) findViewById(R.id.text); mDebugView = (TextView) findViewById(R.id.dbg); mClockView = (TextView) findViewById(R.id.clock); + mGridView = (GridViewPager) findViewById(R.id.grid); + // mGridView.setBackgroundColor(Color.WHITE); + // mGridView.setDrawingCacheBackgroundColor(Color.WHITE); + // mGridView.setOverScrollMode(View.OVER_SCROLL_NEVER); updateDisplay(); } @@ -112,7 +100,7 @@ public class MainActivity extends WearableActivity != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 1); } else { - createSurface(); + createSurfaces(); } Log.d("onStart", "BLE: " + getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)); @@ -129,7 +117,7 @@ public class MainActivity extends WearableActivity public void onRequestPermissionsResult(int rq, @NonNull String[] ps, @NonNull int[] rs) { if(rq == 1) { if (rs[0] == PackageManager.PERMISSION_GRANTED) { - createSurface(); + createSurfaces(); } else { mDebugView.setText("No audio"); } @@ -139,39 +127,58 @@ public class MainActivity extends WearableActivity @Override public void onEnterAmbient(Bundle ambientDetails) { super.onEnterAmbient(ambientDetails); + mHandler.removeCallbacks(updateTimeNotAmbient); updateDisplay(); } - @Override - public void onUpdateAmbient() { - super.onUpdateAmbient(); - mClockView.setText(AMBIENT_DATE_FORMAT.format(new Date())); - } - @Override public void onExitAmbient() { - updateDisplay(); super.onExitAmbient(); + updateDisplay(); + mHandler.postDelayed(updateTimeNotAmbient, TIME_UPDATE_NOT_AMBIENT_PERIOD); } private void updateDisplay() { final int black = getResources().getColor(android.R.color.black,null); - mClockView.setText(AMBIENT_DATE_FORMAT.format(new Date())); + final int white = getResources().getColor(android.R.color.white,null); + updateTime(); if (isAmbient()) { - final int white = getResources().getColor(android.R.color.white,null); - mOuterContainer.setBackgroundColor(black); + mTextContainer.setBackgroundColor(black); mTextView.setTextColor(white); mClockView.setTextColor(white); mDebugView.setTextColor(white); } else { - mOuterContainer.setBackground(null); + mTextContainer.setBackgroundColor(white); mTextView.setTextColor(black); mClockView.setTextColor(black); mDebugView.setTextColor(black); } } + @Override + public void onUpdateAmbient() { + super.onUpdateAmbient(); + updateTime(); + } + + private Runnable updateTimeNotAmbient = new Runnable() { + @Override + public void run() { + updateTime(); + mHandler.postDelayed(this, TIME_UPDATE_NOT_AMBIENT_PERIOD); + } + }; + + private void updateTime() { + mClockView.post(new Runnable() { + @Override + public void run() { + mClockView.setText(AMBIENT_DATE_FORMAT.format(new Date())); + } + }); + } + @Override public void onPause() { Log.d("onPause", "top"); @@ -181,7 +188,7 @@ public class MainActivity extends WearableActivity @Override public void onStop() { Log.d("onStop", "top"); - removeSurface(); + removeSurfaces(); super.onStop(); } diff --git a/app/src/main/java/com/acmetensortoys/watchviz/MainGridViewAdapter.java b/app/src/main/java/com/acmetensortoys/watchviz/MainGridViewAdapter.java new file mode 100644 index 0000000..eed0c38 --- /dev/null +++ b/app/src/main/java/com/acmetensortoys/watchviz/MainGridViewAdapter.java @@ -0,0 +1,166 @@ +/* + AcmeTensorToys WatchViz: a sound visualization application for Android Wear + Copyright (C) 2016 Nathaniel Wesley Filardo + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +/* This file is responsible for navigation of the UI, through different renderers + and (eventually), their settings, as well as global preferences and all that. + */ + + +package com.acmetensortoys.watchviz; + +import android.content.Context; +import android.support.wearable.view.GridPagerAdapter; +import android.support.wearable.view.GridViewPager; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.acmetensortoys.watchviz.vizlib.AudioCanvas; +import com.acmetensortoys.watchviz.vizlib.AudioProvider; +import com.acmetensortoys.watchviz.vizlib.Rendering; +import com.acmetensortoys.watchviz.vizlib.rendering.Grid; +import com.acmetensortoys.watchviz.vizlib.rendering.WholeMax; + +public class MainGridViewAdapter + extends GridPagerAdapter + implements GridViewPager.OnPageChangeListener { + + /* All returned Objects are actually Pages so we know what to do with them later */ + private abstract static class InstantiatedPage { + abstract public boolean ownsview(View v); + abstract public void deinstantiate(MainGridViewAdapter m, ViewGroup g); + }; + private static class ViewInstantiatedPage extends InstantiatedPage { + public View v; + public ViewInstantiatedPage(View v) { this.v = v; } + @Override + public boolean ownsview(View v2) { return v.equals(v2); } + @Override + public void deinstantiate(MainGridViewAdapter m, ViewGroup g) { g.removeView(v); } + } + private abstract static class Page { + public String title; + abstract public InstantiatedPage instantiate(MainGridViewAdapter m, ViewGroup g, int r, int c); + } + private static class RendererPage extends Page { + private Class re; + public RendererPage(Class r) { + this.re = r; + this.title = r.getSimpleName(); + } + public InstantiatedPage instantiate(MainGridViewAdapter mgva, ViewGroup g, int r, int c) { + SurfaceView sv = new SurfaceView(mgva.mCtx); + AudioCanvas ac = new AudioCanvas(r+"x"+c, sv, mgva.mAp); + try { + ac.setRendering(re.getConstructor().newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + g.addView(sv, -1, + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + return new ViewInstantiatedPage(sv); + } + } + + private Page gridPage = new RendererPage(Grid.class); + private Page wholePage = new RendererPage(WholeMax.class); + private final Page[][] pages = { + {gridPage, gridPage}, + {wholePage } + }; + + private final Context mCtx; + private final AudioProvider mAp; + private final TextView mDv; + + public MainGridViewAdapter(Context c, AudioProvider ap, TextView dv) { + mCtx = c; + mAp = ap; + mDv = dv; + } + + @Override + public int getRowCount() { + return pages.length; + } + + @Override + public int getCurrentColumnForRow(int row, int currentColumn) { + return 0; + } + + @Override + public int getColumnCount(int i) { + return pages[i].length; + } + + @Override + public Object instantiateItem(ViewGroup container, int row, int col) { + Log.d("MGVA", "instantiate:" + row + "x" + col); + return pages[row][col].instantiate(this, container, row, col); + } + + @Override + public boolean isViewFromObject(View view, Object o) { + return ((InstantiatedPage)o).ownsview(view); + } + + @Override + public void destroyItem(ViewGroup viewGroup, int row, int col, Object o) { + Log.d("MGVA", "destroy:" + row + "x" + col); + ((InstantiatedPage)o).deinstantiate(this, viewGroup); + } + + @Override + public void startUpdate(ViewGroup container) { + // Log.d("MGVA", "startUpdate top"); + super.startUpdate(container); + } + + @Override + public void finishUpdate(ViewGroup container) { + // super.finishUpdate(container); + Log.d("MGVA", "finishUpdate bottom"); + } + + @Override + public void onPageScrolled(int i, int i1, float v, float v1, int i2, int i3) { + // Log.d("MGVA", "scrolled"); + } + + @Override + public void onPageSelected(int row, int col) { + // Log.d("MGVA", "selected"); + final String name = pages[row][col].title; + mDv.post(new Runnable() { + @Override + public void run() { + mDv.setText(name.substring(0, Math.min(10, name.length()))); + } + }); + } + + @Override + public void onPageScrollStateChanged(int i) { + ; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ebf1030..ff46946 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,64 +1,83 @@ - + tools:deviceIds="wear" + android:id="@+id/outermost" + android:background="@color/blue"> - + + + + - - + android:id="@+id/grid" + android:layout_alignParentLeft="true" + android:layout_marginLeft="0dp" + android:layout_below="@+id/text_container" + android:background="@color/green" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true"> + - + - + - + - + - - + - - + diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java index 0011e98..6fdae58 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java @@ -2,84 +2,114 @@ package com.acmetensortoys.watchviz.vizlib; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Rect; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; +import android.view.ViewTreeObserver; + public class AudioCanvas implements Renderer { - private AudioProvider mAudioProvider; - private SurfaceView mSurfaceView; - private Rendering rendering; + private final AudioProvider mAp; + private final SurfaceView mSv; + private AudioReceiver mAr; + private Rendering mRend; + private Rect gvr = new Rect(); - public AudioCanvas(SurfaceView surfaceView, AudioProvider ap) { - this.mSurfaceView = surfaceView; - this.mAudioProvider = ap; + public AudioCanvas(final String debug1, SurfaceView sv, AudioProvider ap) { + this.mAp = ap; + this.mSv = sv; + sv.getHolder().addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(final SurfaceHolder h) { + Log.d("shc:" + debug1, "Surface Created"); + Canvas c = h.lockCanvas(); + c.drawColor(Color.RED); + h.unlockCanvasAndPost(c); - mSurfaceView.getHolder().addCallback(shc); + synchronized(AudioCanvas.this) { + if (mAr != null) { + Log.d("shc:" + debug1, "Create with existing mAr?"); + mAp.remove(mAr); + } + mAr = new AudioReceiver() { + @Override + public void onAudio(float[] audio, float[] fft) { + final Rendering rcb; + synchronized (AudioCanvas.this) { + rcb = mRend; + if (rcb == null) { + mAp.remove(this); + } + } + Canvas cv = h.lockCanvas(); + if (cv == null) { + // the surface must have been destroyed out from under us; + // just stop here, on the presumption that we will be + // removed from the provider by the hook below + return; + } + cv.drawColor(Color.BLACK); + rcb.render(cv, audio, fft); + h.unlockCanvasAndPost(cv); + } + }; + } + } + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d("shc:"+debug1, "Surface Changed:"+format+":"+width+":"+height); + ; + } + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d("shc:"+debug1, "Surface Destroyed"); + synchronized(AudioCanvas.this) { + mAp.remove(mAr); + mAr = null; + } + } + }); + sv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + manageSubscription(); + return true; + } + }); + sv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final Rendering rcb; + synchronized(AudioCanvas.this) { rcb = mRend; } + if (rcb != null) { + rcb.onClick(); + } + } + }); } public void setRendering(Rendering r) { - synchronized(this) { rendering = r; } - } - - public void onClick() { - final Rendering rcb; - synchronized(this) { rcb = rendering; } - rcb.onClick(); + synchronized(AudioCanvas.this) { + mRend = r; + manageSubscription(); + } } - - // The surface to which this callback is bound is created only after audio permissions - // have been checked. We therefore can simply start in the created callback. Stopping - // happens in onPause below, which fires before the surface is destroyed. - private SurfaceHolder.Callback shc = new SurfaceHolder.Callback() { - private AudioReceiver ar; - - @Override - public void surfaceCreated(final SurfaceHolder h) { - Log.d("shc", "Surface Created"); - Canvas c = h.lockCanvas(); - c.drawColor(Color.RED); - h.unlockCanvasAndPost(c); - - synchronized(this) { - if (ar != null) { - Log.d("shc", "Create with existing ar?"); - mAudioProvider.remove(ar); + private void manageSubscription() { + boolean gvrf = mSv.getGlobalVisibleRect(gvr); + // Log.d("asv:"+debug1,"manageSubscription:gvrf="+gvrf+":gvr="+gvr.toString()); + synchronized(AudioCanvas.this) { + if (mAr != null) { + // Surface has been created + if (gvrf && mRend != null) { + // Visible and have rendering + mAp.add(mAr); + } else { + // Invisible or no rendering set + mAp.remove(mAr); } - ar = new AudioReceiver() { - @Override - public void onAudio(float[] audio, float[] fft) { - final Rendering rcb; - synchronized (this) { rcb = rendering; } - Canvas cv = h.lockCanvas(); - if (cv == null) { - // the surface must have been destroyed out from under us; - // just stop here, on the presumption that we will be - // removed from the provider by the hook below - return; - } - cv.drawColor(Color.BLACK); - rcb.render(cv, audio, fft); - h.unlockCanvasAndPost(cv); - } - }; - mAudioProvider.add(ar); } } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.d("shc", "Surface Changed"); - ; - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.d("shc", "Surface Destroyed"); - synchronized(this) { - mAudioProvider.remove(ar); - ar = null; - } - } - }; + } } diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java index fcbef8b..7eafa4e 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java @@ -7,45 +7,55 @@ import android.util.Log; import org.jtransforms.fft.FloatFFT_1D; -import java.util.Collections; import java.util.HashSet; import java.util.Set; public class AudioProvider { + private static final int AUDIO_SAMPLE_HZ = 11025; private static final int AUDIO_RECORDER_BUFFER_SIZE = 2048; private static final int AUDIO_SAMPLES = 512; + // AudioReceiver callbacks run every (AUDIO_SAMPLES / AUDIO_SAMPLE_HZ) seconds. + private Set audioReceivers; private Thread audioSourceThread; public void add(AudioReceiver ar) { synchronized(this) { boolean empty = audioReceivers.isEmpty(); - audioReceivers.add(ar); + boolean did = audioReceivers.add(ar); if (empty) { + Log.d("AudioProvider", "Starting audio source thread..."); this.start(); + } else if (did) { + Log.d("AudioProvider", "Added receiver; now=" + audioReceivers.size()); } } } - public void remove(AudioReceiver ar) { synchronized(this) { - audioReceivers.remove(ar); + boolean did = audioReceivers.remove(ar); + if(!did) { + // Nothing to do! + return; + } if(audioReceivers.isEmpty()) { + Log.d("AudioProvider", "Stopping audio source thread..."); this.stop(); + } else { + Log.d("AudioProvider", "Removed receiver; left=" + audioReceivers.size()); } } } - public AudioProvider() { - audioReceivers = Collections.synchronizedSet(new HashSet()); + audioReceivers = new HashSet(); } private void start() { final AudioRecord ar = new AudioRecord( MediaRecorder.AudioSource.MIC, - 11025, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT, + AUDIO_SAMPLE_HZ, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT, AUDIO_RECORDER_BUFFER_SIZE); Log.d("shc", "Audio session ID:" + ar.getAudioSessionId()); @@ -65,21 +75,33 @@ public class AudioProvider { System.arraycopy(samples, 0, fft, 0, AUDIO_SAMPLES); fftc.realForward(fft); - for(AudioReceiver ar : audioReceivers) { + // Snapshot the current callbacks and iterate that. This prevents + // concurrent mutation exceptions, at the cost of being kind of terrible. + Set ars; + synchronized(this) { + ars = new HashSet<>(audioReceivers); + } + for (AudioReceiver ar : ars) { ar.onAudio(samples, fft); } } } }; + audioSourceThread.start(); } - private void stop() { synchronized(this) { - audioSourceThread.interrupt(); - - try { audioSourceThread.join(); } - catch (InterruptedException ie) { ; } - finally { audioSourceThread = null; } + if (audioSourceThread != null) { + audioSourceThread.interrupt(); + + try { + audioSourceThread.join(); + } catch (InterruptedException ie) { + ; + } finally { + audioSourceThread = null; + } + } } } } diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java index 5045750..f59b499 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java @@ -4,5 +4,5 @@ import android.graphics.Canvas; public abstract class Rendering { abstract public void render(Canvas c, float[] audio, float[] fft); - public void onClick() { } + public void onClick() { ; } } \ No newline at end of file diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java deleted file mode 100644 index e401ca5..0000000 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.acmetensortoys.watchviz.vizlib; - -import android.widget.TextView; - -import com.acmetensortoys.watchviz.vizlib.rendering.Grid; -import com.acmetensortoys.watchviz.vizlib.rendering.WholeMax; - -import java.util.ArrayList; - -public class RenderingSelector { - private TextView mDebugView; - private Renderer mRenderer; - - private int renderCBix; - private ArrayList> cbs = new ArrayList<>(); - - public RenderingSelector(TextView dv, Renderer r) { - mDebugView = dv; - mRenderer = r; - cbs.add(Grid.class); - cbs.add(WholeMax.class); - renderCBix = 0; - _setCyclerCBByIx(); - } - - private void _setCyclerCB(Class next) { - Rendering rendering; - try { - rendering = next.getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - mRenderer.setRendering(rendering); - String name = rendering.getClass().getSimpleName(); - mDebugView.setText(name.substring(0,Math.min(10,name.length()))); - } - private void _setCyclerCBByIx() { - _setCyclerCB(cbs.get(renderCBix)); - } - public void nextCyclerCB() { - synchronized(this) { - if (renderCBix == cbs.size()-1) { - renderCBix = 0; - } else { - renderCBix += 1; - } - _setCyclerCBByIx(); - } - } - public void prevCyclerCB() { - synchronized(this) { - if (renderCBix == 0) { - renderCBix = cbs.size()-1; - } else { - renderCBix -= 1; - } - _setCyclerCBByIx(); - } - } - public void setCBIx(int ix) { - if ((ix < 0) || (ix >= cbs.size())) { - throw new ArrayIndexOutOfBoundsException(ix); - } - synchronized(this) { - renderCBix = ix; - _setCyclerCBByIx(); - } - } -} -- 2.50.1