/*
* Copyright (c) 2013, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
package com.marshalchen.common.uimodule.rebound.ui;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import com.marshalchen.common.commonUtils.logUtils.Logs;
import com.marshalchen.common.uimodule.rebound.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.marshalchen.common.uimodule.rebound.ui.Util.*;
/**
* The SpringConfiguratorView provides a reusable view for live-editing all registered springs
* within an Application. Each registered Spring can be accessed by its id and its tension and
* friction properties can be edited while the user tests the effected UI live.
*/
public class SpringConfiguratorView extends FrameLayout {
private static final int MAX_SEEKBAR_VAL = 100000;
private static final float MIN_TENSION = 0;
private static final float MAX_TENSION = 200;
private static final float MIN_FRICTION = 0;
private static final float MAX_FRICTION = 50;
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#");
private final SpinnerAdapter spinnerAdapter;
private final List<SpringConfig> mSpringConfigs = new ArrayList<SpringConfig>();
private final Spring mRevealerSpring;
private final float mStashPx;
private final float mRevealPx;
private final SpringConfigRegistry springConfigRegistry;
private final int mTextColor = Color.argb(255, 225, 225, 225);
private SeekBar mTensionSeekBar;
private SeekBar mFrictionSeekBar;
private Spinner mSpringSelectorSpinner;
private TextView mFrictionLabel;
private TextView mTensionLabel;
private SpringConfig mSelectedSpringConfig;
public SpringConfiguratorView(Context context) {
this(context, null);
}
public SpringConfiguratorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public SpringConfiguratorView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
SpringSystem springSystem = SpringSystem.create();
springConfigRegistry = SpringConfigRegistry.getInstance();
spinnerAdapter = new SpinnerAdapter(context);
Resources resources = getResources();
mRevealPx = dpToPx(40, resources);
mStashPx = dpToPx(280, resources);
mRevealerSpring = springSystem.createSpring();
SpringListener revealerSpringListener = new RevealerSpringListener();
mRevealerSpring
.setCurrentValue(1)
.setEndValue(1)
.addListener(revealerSpringListener);
addView(generateHierarchy(context));
SeekbarListener seekbarListener = new SeekbarListener();
mTensionSeekBar.setMax(MAX_SEEKBAR_VAL);
mTensionSeekBar.setOnSeekBarChangeListener(seekbarListener);
mFrictionSeekBar.setMax(MAX_SEEKBAR_VAL);
mFrictionSeekBar.setOnSeekBarChangeListener(seekbarListener);
mSpringSelectorSpinner.setAdapter(spinnerAdapter);
mSpringSelectorSpinner.setOnItemSelectedListener(new SpringSelectedListener());
refreshSpringConfigurations();
this.setTranslationY(mStashPx);
}
/**
* Programmatically build up the view hierarchy to avoid the need for resources.
*
* @return View hierarchy
*/
private View generateHierarchy(Context context) {
Resources resources = getResources();
LayoutParams params;
int fivePx = dpToPx(5, resources);
int tenPx = dpToPx(10, resources);
int twentyPx = dpToPx(20, resources);
TableLayout.LayoutParams tableLayoutParams = new TableLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f);
tableLayoutParams.setMargins(0, 0, fivePx, 0);
LinearLayout seekWrapper;
FrameLayout root = new FrameLayout(context);
params = createLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpToPx(300, resources));
root.setLayoutParams(params);
FrameLayout container = new FrameLayout(context);
params = createMatchParams();
params.setMargins(0, twentyPx, 0, 0);
container.setLayoutParams(params);
container.setBackgroundColor(Color.argb(100, 0, 0, 0));
root.addView(container);
mSpringSelectorSpinner = new Spinner(context, Spinner.MODE_DIALOG);
params = createMatchWrapParams();
params.gravity = Gravity.TOP;
params.setMargins(tenPx, tenPx, tenPx, 0);
mSpringSelectorSpinner.setLayoutParams(params);
container.addView(mSpringSelectorSpinner);
LinearLayout linearLayout = new LinearLayout(context);
params = createMatchWrapParams();
params.setMargins(0, 0, 0, dpToPx(80, resources));
params.gravity = Gravity.BOTTOM;
linearLayout.setLayoutParams(params);
linearLayout.setOrientation(LinearLayout.VERTICAL);
container.addView(linearLayout);
seekWrapper = new LinearLayout(context);
params = createMatchWrapParams();
params.setMargins(tenPx, tenPx, tenPx, twentyPx);
seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
seekWrapper.setLayoutParams(params);
seekWrapper.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.addView(seekWrapper);
mTensionSeekBar = new SeekBar(context);
mTensionSeekBar.setLayoutParams(tableLayoutParams);
seekWrapper.addView(mTensionSeekBar);
mTensionLabel = new TextView(getContext());
mTensionLabel.setTextColor(mTextColor);
params = createLayoutParams(
dpToPx(50, resources),
ViewGroup.LayoutParams.MATCH_PARENT);
mTensionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
mTensionLabel.setLayoutParams(params);
mTensionLabel.setMaxLines(1);
seekWrapper.addView(mTensionLabel);
seekWrapper = new LinearLayout(context);
params = createMatchWrapParams();
params.setMargins(tenPx, tenPx, tenPx, twentyPx);
seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
seekWrapper.setLayoutParams(params);
seekWrapper.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.addView(seekWrapper);
mFrictionSeekBar = new SeekBar(context);
mFrictionSeekBar.setLayoutParams(tableLayoutParams);
seekWrapper.addView(mFrictionSeekBar);
mFrictionLabel = new TextView(getContext());
mFrictionLabel.setTextColor(mTextColor);
params = createLayoutParams(dpToPx(50, resources), ViewGroup.LayoutParams.MATCH_PARENT);
mFrictionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
mFrictionLabel.setLayoutParams(params);
mFrictionLabel.setMaxLines(1);
seekWrapper.addView(mFrictionLabel);
View nub = new View(context);
params = createLayoutParams(dpToPx(60, resources), dpToPx(40, resources));
params.gravity = Gravity.TOP | Gravity.CENTER;
nub.setLayoutParams(params);
nub.setOnTouchListener(new OnNubTouchListener());
nub.setBackgroundColor(Color.argb(255, 0, 164, 209));
root.addView(nub);
return root;
}
/**
* remove the configurator from its parent and clean up springs and listeners
*/
public void destroy() {
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.removeView(this);
}
mRevealerSpring.destroy();
}
/**
* reload the springs from the registry and update the UI
*/
public void refreshSpringConfigurations() {
Map<SpringConfig, String> springConfigMap = springConfigRegistry.getAllSpringConfig();
spinnerAdapter.clear();
mSpringConfigs.clear();
for (Map.Entry<SpringConfig, String> entry : springConfigMap.entrySet()) {
if (entry.getKey() == SpringConfig.defaultConfig) {
continue;
}
mSpringConfigs.add(entry.getKey());
spinnerAdapter.add(entry.getValue());
}
// Add the default config in last.
mSpringConfigs.add(SpringConfig.defaultConfig);
spinnerAdapter.add(springConfigMap.get(SpringConfig.defaultConfig));
spinnerAdapter.notifyDataSetChanged();
if (mSpringConfigs.size() > 0) {
mSpringSelectorSpinner.setSelection(0);
}
}
private class SpringSelectedListener implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
mSelectedSpringConfig = mSpringConfigs.get(i);
updateSeekBarsForSpringConfig(mSelectedSpringConfig);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
}
/**
* listen to events on seekbars and update registered springs accordingly
*/
private class SeekbarListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int val, boolean b) {
float tensionRange = MAX_TENSION - MIN_TENSION;
float frictionRange = MAX_FRICTION - MIN_FRICTION;
if (seekBar == mTensionSeekBar) {
float scaledTension = ((val) * tensionRange) / MAX_SEEKBAR_VAL + MIN_TENSION;
mSelectedSpringConfig.tension =
OrigamiValueConverter.tensionFromOrigamiValue(scaledTension);
String roundedTensionLabel = DECIMAL_FORMAT.format(scaledTension);
mTensionLabel.setText("T:" + roundedTensionLabel);
}
if (seekBar == mFrictionSeekBar) {
float scaledFriction = ((val) * frictionRange) / MAX_SEEKBAR_VAL + MIN_FRICTION;
mSelectedSpringConfig.friction =
OrigamiValueConverter.frictionFromOrigamiValue(scaledFriction);
String roundedFrictionLabel = DECIMAL_FORMAT.format(scaledFriction);
mFrictionLabel.setText("F:" + roundedFrictionLabel);
}
Logs.d("tension--" + mSelectedSpringConfig.friction + " " + mSelectedSpringConfig.tension);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
/**
* update the position of the seekbars based on the spring value;
*
* @param springConfig current editing spring
*/
private void updateSeekBarsForSpringConfig(SpringConfig springConfig) {
float tension = (float) OrigamiValueConverter.origamiValueFromTension(springConfig.tension);
float tensionRange = MAX_TENSION - MIN_TENSION;
int scaledTension = Math.round(((tension - MIN_TENSION) * MAX_SEEKBAR_VAL) / tensionRange);
float friction = (float) OrigamiValueConverter.origamiValueFromFriction(springConfig.friction);
float frictionRange = MAX_FRICTION - MIN_FRICTION;
int scaledFriction = Math.round(((friction - MIN_FRICTION) * MAX_SEEKBAR_VAL) / frictionRange);
mTensionSeekBar.setProgress(scaledTension);
mFrictionSeekBar.setProgress(scaledFriction);
}
/**
* toggle visibility when the nub is tapped.
*/
private class OnNubTouchListener implements OnTouchListener {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
togglePosition();
}
return true;
}
}
private void togglePosition() {
double currentValue = mRevealerSpring.getEndValue();
mRevealerSpring
.setEndValue(currentValue == 1 ? 0 : 1);
}
private class RevealerSpringListener implements SpringListener {
@Override
public void onSpringUpdate(Spring spring) {
float val = (float) spring.getCurrentValue();
float minTranslate = mRevealPx;
float maxTranslate = mStashPx;
float range = maxTranslate - minTranslate;
float yTranslate = (val * range) + minTranslate;
SpringConfiguratorView.this.setTranslationY(yTranslate);
}
@Override
public void onSpringAtRest(Spring spring) {
}
@Override
public void onSpringActivate(Spring spring) {
}
@Override
public void onSpringEndStateChange(Spring spring) {
}
}
private class SpinnerAdapter extends BaseAdapter {
private final Context mContext;
private final List<String> mStrings;
public SpinnerAdapter(Context context) {
mContext = context;
mStrings = new ArrayList<String>();
}
@Override
public int getCount() {
return mStrings.size();
}
@Override
public Object getItem(int position) {
return mStrings.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public void add(String string) {
mStrings.add(string);
notifyDataSetChanged();
}
/**
* Remove all elements from the list.
*/
public void clear() {
mStrings.clear();
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView;
if (convertView == null) {
textView = new TextView(mContext);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
textView.setLayoutParams(params);
int twelvePx = dpToPx(12, getResources());
textView.setPadding(twelvePx, twelvePx, twelvePx, twelvePx);
textView.setTextColor(mTextColor);
} else {
textView = (TextView) convertView;
}
textView.setText(mStrings.get(position));
return textView;
}
}
}