Skip to content

Commit

Permalink
Merge branch 'release-1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
markormesher committed Jun 2, 2016
2 parents e7822e6 + 8e6a8cc commit 2c481c9
Show file tree
Hide file tree
Showing 8 changed files with 9,402 additions and 48 deletions.
91 changes: 65 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This library provides a clickable floating action button (FAB). The FAB can trig
- [Demo](#demo)
- [Installation](#installation)
- [Usage & Customisation](#usage--customisation)
- [Links](#links)
- [Links & Other Projects](#links--other-projects)

## Demo

Expand Down Expand Up @@ -42,7 +42,7 @@ You can try the demo yourself in one of two ways:

## Usage & Customisation

**Note:** all of the instructions below assume that the Floating Action Button is reference by the variable `fab`, i.e.
**Note:** all of the instructions below assume that the Floating Action Button is referenced by the variable `fab`, i.e.

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

Expand All @@ -59,13 +59,13 @@ The `FloatingActionButton` view must be placed at the **root** of your layout, *

The icon displayed in the centre of the FAB should be set with `fab.setIcon(...)`, passing in the `View`, `Drawable` or `Drawable` resource ID to use. This `View` will be centred in a 24dp x 24dp view group, as per the Android Material Design specs.

See the note below on [state preservation](#note-state-preservation).
:warning: See the note below on [state preservation](#note-state-preservation).

### FAB Background Colour

The background colour to be used for the FAB should be set with `fab.setBackgroundColor(...)`, passing in an RGBa colour value (e.g. `0xffff9900` for a dark orange). Note that this method does **not** take a colour resource ID, so passing in `R.color.some_colour_name` will not work.

See the note below on [state preservation](#note-state-preservation).
:warning: See the note below on [state preservation](#note-state-preservation).

### FAB Click Listener

Expand All @@ -77,46 +77,79 @@ A click listener can be added to the FAB in the same way as any other button:
// ...
}
});

or

fab.setOnClickListener(v -> {
// ...
});

### Speed-Dial Menus

The speed-dial menu can be enabled by creating a class that extends `SpeedDialMenuAdapter` and then calling `setAdapter(...)` on the FAB. This will remove any click listener on the FAB and enable the speed-dial menu.
The view `v` passed in the callback can be safely cast to `FloatingActionButton`.

The adapter class has several methods that can be overridden to control the menu:
:warning: See the note below on [click action priority](#note-click-action-priority).

##### `int getCount()`
### Speed-Dial Menus

...**must** be overridden to return the number of menu items.
The speed-dial menu can be enabled by creating a class that extends `SpeedDialMenuAdapter` and then calling `setAdapter(...)` on the FAB.

##### `MenuItem getViews(Context context, int position)`
The adapter class has several methods that can be overridden to control the menu:

...**must** be overridden to return a `MenuItem` wrapper for the given position. This method will be called to create a wrapper once per menu item. The wrapper allows the icon for a menu item to be specified as a `View`, a `Drawable` or a `Drawable` ID, and allows the label to be specified as a `View`, a `String` or a `String` ID.
|Method | Description|
|:--- | :---|
|`int getCount()` | **must** be overridden to return the number of menu items.|
|`MenuItem getViews(Context context, int position)` | **must** be overridden to return a `MenuItem` wrapper for the given position. This method will be called to create a wrapper once per menu item. The wrapper allows the icon for a menu item to be specified as a `View`, a `Drawable` or a `Drawable` ID, and allows the label to be specified as a `View`, a `String` or a `String` ID.<br /><br />If multiple properties are specified for the icon or label, the first non-null in the order [`View`, `Drawable` or `String`, resource ID] will be applied. No parameters are required: all label fields could be left as `null` to produce an icon-only menu item for example; everything could be left as `null` to produce a blank menu item, but that would be quite useless.
|`int getBackgroundColour(int position)` | **may** be overridden to return the background colour that should be used for the disc at the given position.|
|`boolean onMenuItemClick(int position)` | **may** be overridden to listen for clicks on the individual menu items. Return `true` to close the menu after the click has been handled (the default behaviour) or `false` to leave it open.|
|`boolean rotateFab()` | **may** be overridden to specify whether the FAB should rotate by 1/8th of a turn when the speed-dial menu opens. This is useful for smoothly transitioning between a '+' and 'x' icon.|
|`boolean isEnabled()` | **may** be overridden to enable or disable the speed-dial menu. This is useful for temporarily disabling the speed-dial menu without having to set the adapter to `null`.|

If multiple properties are specified for the icon or label, the first non-null in the order [`View`, `Drawable` or `String`, resource ID] will be applied. No parameters are required: all label fields could be left as `null` to produce an icon-only menu item for example; everything could be left as `null` to produce a blank menu item, but that would be quite useless.
**Note:** for all methods, the view at position `0` is the furthest away from the FAB; the view at `getCount() - 1` is the closest.

##### `int getBackgroundColour(int position)`
If the state or functionality has changed such that `getCount()` or `getViews(...)` will have a different output, `fab.rebuildSpeedDialMenu()` must be called to regenerate the speed-dial menu item views. If `getCount()` returns zero when `rebuildSpeedDialMenu()` is called the speed-dial menu will be disabled until it is called again and `getCount()` returns a number greater than zero.

...**may** be overridden to return the background colour that should be used for the disc at the given position.
:warning: See the note below on [click action priority](#note-click-action-priority).

##### `boolean onMenuItemClick(int position)`
#### State Change Listeners

...**may** be overridden to listen for clicks on the individual menu items. Return `true` to close the menu after the click has been handled (the default behaviour) or `false` to leave it open
Two state change listeners are provided to monitor when the speed-dial menu opens or closes. These can be used as follows:

##### `boolean rotateFab()`
fab.setOnSpeedDialOpenListener(new FloatingActionButton.OnSpeedDialOpenListener() {
@Override
public void onOpen(FloatingActionButton v) {
// ...
}
});
fab.setOnSpeedDialCloseListener(new FloatingActionButton.OnSpeedDialCloseListener() {
@Override
public void onClose(FloatingActionButton v) {
// ...
}
});

or

fab.setOnSpeedDialOpenListener(v -> {
// ...
});
fab.setOnSpeedDialCloseListener(v -> {
// ...
});

...**may** be overridden to specify whether the FAB should rotate by 1/8th of a turn when the speed-dial menu opens. This is useful for smoothly transitioning between a '+' and 'x' icon.
### Controls

---
The FAB can be hidden and shown with the `fab.hide()` and `fab.show()` methods, and the method `fab.isShown()` will return a boolean indicating the current state. These methods animate the FAB in and out of visibility. If the speed-dial menu is open when `.hide()` is called it will be closed.

**Note:** for all methods, the view at position `0` is the furthest away from the FAB; the view at `getCount() - 1` is the closest.
The speed-dial menu can be manually opened and closed with `fab.openSpeedDialMenu()` and `fab.closeSpeedDialMenu()`. These methods will do nothing if no speed-dial menu adapter is set, if the FAB is hidden, or if they are called when the menu is already in the indicated state (i.e. `fab.openSpeedDialMenu()` will do nothing if the menu is already open).

If the functionality has changed such that `getCount()` or `getViews(...)` will have a different output, `fab.rebuildSpeedDialMenu()` must be called to regenerate the speed-dial menu item views.
### Note: Click Action Priority

### Controls
As per Material Design specs, the FAB functions as a regular button **or** a trigger for the speed-dial menu, but not both. For this reason, the click listener and the speed-dial menu are never invoked at the same time.

The FAB can be hidden and shown with the `fab.hide()` and `fab.show()` methods, and the method `fab.isShown()` will return a boolean indicating the current state. These methods animate the FAB in and out of visibility. If the speed-dial menu is open when `.hide()` is called it will be closed.
The speed-dial menu is given priority: when the FAB is clicked the speed-dial menu will be shown if the speed-dial menu adapter is non-null **and** the adapter's `isEnabled()` function returns true. Otherwise, the FAB's click listener will be called (if it has been set).

The speed-dial menu can be manually opened and closed with `fab.openSpeedDialMenu()` and `fab.closeSpeedDialMenu()`. These methods will do nothing if no speed-dial menu adapter is set, if the FAB is hidden, or if they are called when the menu is already in the indicated state (i.e. `fab.openSpeedDialMenu()` will do nothing if the menu is already open).
Setting a speed-dial menu adapter does not remove the click listener, and setting a click listener does not remove the speed-dial menu adapter. For an example of how the two operation modes interact, check the demo app's source code.

To receive state change updates when the speed-dial menu is opened or closed, use the open/close listeners described above.

### Note: State Preservation

Expand All @@ -126,4 +159,10 @@ When a configuration change happens within your app and the activity/fragment is
- the FAB icon, **if and only if** it was set as a `Drawable` resource ID;
- the FAB background colour.

All other properties (`View`/`Drawable` icons, speed-dial menu adapters, etc.) will need to be restored "manually". The demo application shows how this can be accomplished.
All other properties (`View`/`Drawable` icons, speed-dial menu adapters, etc.) will need to be restored "manually". The demo application shows how this can be accomplished.

## Links & Other Projects

- [Mark Ormesher](http://markormesher.co.uk) - My personal site
- [Android UI Tooltips](https://github.com/markormesher/android-tooltips) - An Android library providing a easy-to-use UI tooltips
- [Android Utils](https://github.com/markormesher/android-utils) - An Android library providing a collection of utility and helper methods
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ buildscript {
}

def version = new Properties()
version['code'] = 7
version['name'] = '1.1.1'
version['code'] = 8
version['name'] = '1.2.0'

def secrets = getSecrets()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ public void onCreate(Bundle savedInstanceState) {
fab.setIcon(icons[iconSelected]);
fab.setBackgroundColour(colours[colourSelected]);
}
updateFabMode();
fab.setMenuAdapter(new SpeedDialAdapter());
fab.setOnClickListener(iv -> Toast.makeText(DemoActivity.this, R.string.click_simple, Toast.LENGTH_SHORT).show());
fab.setOnSpeedDialOpenListener(f -> Toast.makeText(DemoActivity.this, R.string.speed_dial_opened, Toast.LENGTH_SHORT).show());
fab.setOnSpeedDialCloseListener(f -> Toast.makeText(DemoActivity.this, R.string.speed_dial_closed, Toast.LENGTH_SHORT).show());

// get references to buttons
hideShowButton = (Button) findViewById(R.id.hide_show);
Expand All @@ -106,7 +109,6 @@ public void onCreate(Bundle savedInstanceState) {
switchModeButton.setOnClickListener(v -> {
inClickMode = !inClickMode;
updateButtonStates();
updateFabMode();
});

findViewById(R.id.change_icon).setOnClickListener(v -> {
Expand Down Expand Up @@ -158,14 +160,6 @@ private void updateButtonStates() {
openSpeedDialButton.setEnabled(!inClickMode);
}

private void updateFabMode() {
if (inClickMode) {
fab.setOnClickListener(iv -> Toast.makeText(DemoActivity.this, R.string.click_simple, Toast.LENGTH_SHORT).show());
} else {
fab.setMenuAdapter(new SpeedDialAdapter());
}
}

private class SpeedDialAdapter extends SpeedDialMenuAdapter {

@Override
Expand Down Expand Up @@ -251,5 +245,10 @@ protected boolean onMenuItemClick(int position) {
protected boolean rotateFab() {
return iconSelected == 0;
}

@Override
protected boolean isEnabled() {
return !inClickMode;
}
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values/values.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<string name="toggle_optional_close_off">Disable Optional Speed-Dial Close</string>
<string name="open_speed_dial">Open Speed Dial</string>

<string name="speed_dial_opened">Speed-dial opened!</string>
<string name="speed_dial_closed">Speed-dial closed!</string>

<string name="speed_dial_label">Option #%d</string>

<string name="label_optional_close">Optional Close</string>
Expand Down
4 changes: 2 additions & 2 deletions fab/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ buildscript {
}

def version = new Properties()
version['code'] = 7
version['name'] = '1.1.1'
version['code'] = 8
version['name'] = '1.2.0'

apply plugin: 'me.tatarka.retrolambda'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,27 +213,65 @@ public boolean isShown() {
* Interaction *
*=============*/

private OnClickListener listener;
private OnClickListener clickListener;
private OnSpeedDialOpenListener speedDialOpenListener;
private OnSpeedDialCloseListener speedDialCloseListener;

@Override
public void setOnClickListener(OnClickListener listener) {
this.menuAdapter = null;
this.listener = listener;
this.clickListener = listener;
}

public void setOnSpeedDialOpenListener(OnSpeedDialOpenListener listener) {
this.speedDialOpenListener = listener;
}

public void setOnSpeedDialCloseListener(OnSpeedDialCloseListener listener) {
this.speedDialCloseListener = listener;
}

private void onClick() {
if (listener != null) {
listener.onClick(this);
} else if (menuAdapter != null) {
// try to invoke the speed-dial menu first, otherwise run the click listener

if (!speedDialInternallyDisabled && menuAdapter != null && menuAdapter.isEnabled()) {
toggleSpeedDialMenu();
} else if (clickListener != null) {
clickListener.onClick(this);
}
}

/**
* Listener interface for speed-dial menu open action.
*/
public interface OnSpeedDialOpenListener {

/**
* Called when the speed-dial menu is opened.
*
* @param v the {@code FloatingActionButton} view
*/
void onOpen(FloatingActionButton v);
}

/**
* Listener interface for speed-dial menu close action.
*/
public interface OnSpeedDialCloseListener {

/**
* Called when the speed-dial menu is opened.
*
* @param v the {@code FloatingActionButton} view
*/
void onClose(FloatingActionButton v);
}

/*=================*
* Speed-dial menu *
*=================*/

private boolean speedDialMenuOpen = false;
private boolean speedDialInternallyDisabled = false;
private boolean busyAnimatingFabIcon = false;
private boolean busyAnimatingSpeedDialCover = false;
private boolean busyAnimatingSpeedDialMenu = false;
Expand All @@ -250,7 +288,6 @@ private void onClick() {
* @see uk.co.markormesher.android_fab.SpeedDialMenuAdapter
*/
public void setMenuAdapter(SpeedDialMenuAdapter menuAdapter) {
this.listener = null;
this.menuAdapter = menuAdapter;
if (menuAdapter != null) rebuildSpeedDialMenu();
}
Expand All @@ -266,8 +303,10 @@ public void rebuildSpeedDialMenu() {
// sanity check
if (menuAdapter.getCount() == 0) {
Log.w(C.LOG_TAG, "SpeedDialMenuAdapter contained zero items; speed-dial functionality was disabled.");
setMenuAdapter(null);
speedDialInternallyDisabled = true;
return;
} else {
speedDialInternallyDisabled = false;
}

// init empty array
Expand Down Expand Up @@ -360,6 +399,10 @@ private void toggleSpeedDialMenu() {
// flip
speedDialMenuOpen = !speedDialMenuOpen;

// listener
if (speedDialMenuOpen && speedDialOpenListener != null) speedDialOpenListener.onOpen(this);
if (!speedDialMenuOpen && speedDialCloseListener != null) speedDialCloseListener.onClose(this);

// change UI
if (menuAdapter.rotateFab()) toggleFabIconForSpeedDialMenu(speedDialMenuOpen);
setSpeedDialCoverVisible(speedDialMenuOpen);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ protected boolean rotateFab() {
return false;
}

/**
* Returns a boolean indicating whether or not the speed-dial menu is enabled.
* This is useful for disabling the speed-dial menu without clearing the adapter.
*
* @return a boolean indicating whether or not the speed-dial menu is enabled
*/
protected boolean isEnabled() {
return true;
}

/**
* Wrapper class for returning the views to use for a speed-dial menu item.
*/
Expand Down
Loading

0 comments on commit 2c481c9

Please sign in to comment.