1 /*
2  * Copyright 2014, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.floatingactionbuttonbasic;
18 
19 import android.content.Context;
20 import android.graphics.Outline;
21 import android.util.AttributeSet;
22 import android.view.View;
23 import android.view.ViewOutlineProvider;
24 import android.widget.Checkable;
25 import android.widget.FrameLayout;
26 
27 /**
28  * A Floating Action Button is a {@link android.widget.Checkable} view distinguished by a circled
29  * icon floating above the UI, with special motion behaviors.
30  */
31 public class FloatingActionButton extends FrameLayout implements Checkable {
32 
33     /**
34      * Interface definition for a callback to be invoked when the checked state
35      * of a compound button changes.
36      */
37     public static interface OnCheckedChangeListener {
38 
39         /**
40          * Called when the checked state of a FAB has changed.
41          *
42          * @param fabView   The FAB view whose state has changed.
43          * @param isChecked The new checked state of buttonView.
44          */
onCheckedChanged(FloatingActionButton fabView, boolean isChecked)45         void onCheckedChanged(FloatingActionButton fabView, boolean isChecked);
46     }
47 
48     /**
49      * An array of states.
50      */
51     private static final int[] CHECKED_STATE_SET = {
52             android.R.attr.state_checked
53     };
54 
55     private static final String TAG = "FloatingActionButton";
56 
57     // A boolean that tells if the FAB is checked or not.
58     private boolean mChecked;
59 
60     // A listener to communicate that the FAB has changed it's state
61     private OnCheckedChangeListener mOnCheckedChangeListener;
62 
FloatingActionButton(Context context)63     public FloatingActionButton(Context context) {
64         this(context, null, 0, 0);
65     }
66 
FloatingActionButton(Context context, AttributeSet attrs)67     public FloatingActionButton(Context context, AttributeSet attrs) {
68         this(context, attrs, 0, 0);
69     }
70 
FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr)71     public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
72         this(context, attrs, defStyleAttr, 0);
73     }
74 
FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)75     public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr,
76             int defStyleRes) {
77         super(context, attrs, defStyleAttr);
78 
79         setClickable(true);
80 
81         // Set the outline provider for this view. The provider is given the outline which it can
82         // then modify as needed. In this case we set the outline to be an oval fitting the height
83         // and width.
84         setOutlineProvider(new ViewOutlineProvider() {
85             @Override
86             public void getOutline(View view, Outline outline) {
87                 outline.setOval(0, 0, getWidth(), getHeight());
88             }
89         });
90 
91         // Finally, enable clipping to the outline, using the provider we set above
92         setClipToOutline(true);
93     }
94 
95     /**
96      * Sets the checked/unchecked state of the FAB.
97      * @param checked
98      */
setChecked(boolean checked)99     public void setChecked(boolean checked) {
100         // If trying to set the current state, ignore.
101         if (checked == mChecked) {
102             return;
103         }
104         mChecked = checked;
105 
106         // Now refresh the drawable state (so the icon changes)
107         refreshDrawableState();
108 
109         if (mOnCheckedChangeListener != null) {
110             mOnCheckedChangeListener.onCheckedChanged(this, checked);
111         }
112     }
113 
114     /**
115      * Register a callback to be invoked when the checked state of this button
116      * changes.
117      *
118      * @param listener the callback to call on checked state change
119      */
setOnCheckedChangeListener(OnCheckedChangeListener listener)120     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
121         mOnCheckedChangeListener = listener;
122     }
123 
124     @Override
isChecked()125     public boolean isChecked() {
126         return mChecked;
127     }
128 
129     @Override
toggle()130     public void toggle() {
131         setChecked(!mChecked);
132     }
133 
134     /**
135      * Override performClick() so that we can toggle the checked state when the view is clicked
136      */
137     @Override
performClick()138     public boolean performClick() {
139         toggle();
140         return super.performClick();
141     }
142 
143     @Override
onSizeChanged(int w, int h, int oldw, int oldh)144     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
145         super.onSizeChanged(w, h, oldw, oldh);
146 
147         // As we have changed size, we should invalidate the outline so that is the the
148         // correct size
149         invalidateOutline();
150     }
151 
152     @Override
onCreateDrawableState(int extraSpace)153     protected int[] onCreateDrawableState(int extraSpace) {
154         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
155         if (isChecked()) {
156             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
157         }
158         return drawableState;
159     }
160 }
161