/*
* Copyright (C) 2010 Cyril Mottier (http://www.cyrilmottier.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package greendroid.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.cyrilmottier.android.greendroid.R;
/**
* A SegmentedBar displays a set of Buttons. Only one segment in a SegmentedBar
* can be selected at a time. A SegmentedBar is very close to a TabWidget in
* term of functionalities.
*
* @author Cyril Mottier
*/
public class SegmentedBar extends LinearLayout implements OnFocusChangeListener {
/**
* Clients may use this listener to be notified of any changes that occurs
* on the SegmentBar
*
* @author Cyril Mottier
*/
public static interface OnSegmentChangeListener {
/**
* Notification that the current segment has changed.
*
* @param index The index of the new selected segment.
* @param clicked Whether the segment has been selected via a user
* click.
*/
public void onSegmentChange(int index, boolean clicked);
}
private OnSegmentChangeListener mOnSegmentChangeListener;
private int mCheckedSegment;
private Drawable mDividerDrawable;
private int mDividerWidth;
public SegmentedBar(Context context) {
this(context, null);
}
public SegmentedBar(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.gdSegmentedBarStyle);
}
public SegmentedBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
initSegmentedBar();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SegmentedBar, defStyle, 0);
mDividerDrawable = a.getDrawable(R.styleable.SegmentedBar_dividerDrawable);
mDividerWidth = a.getDimensionPixelSize(R.styleable.SegmentedBar_dividerWidth, 0);
a.recycle();
}
private void initSegmentedBar() {
mCheckedSegment = 0;
setOrientation(LinearLayout.HORIZONTAL);
// Register ourselves so that we can handle focus on internal segments
setFocusable(true);
setOnFocusChangeListener(this);
}
/**
* Sets the drawable that is used as divider between each segment.
*
* @param dividerDrawable The drawable to used as a divider. Note : using a
* ColorDrawable will not work properly as the intrinsic width of
* a ColorDrawable is -1.
*/
public void setDividerDrawable(Drawable dividerDrawable) {
mDividerDrawable = dividerDrawable;
}
/**
* Sets the drawable that is used as divider between each segment.
*
* @param resId The identifier of the Drawable to use.
*/
public void setDividerDrawable(int resId) {
mDividerDrawable = getContext().getResources().getDrawable(resId);
}
/**
* Sets the width of the divider that will be used as segment divider. If
* the dividerWidth has not been set, the intrinsic width of the divider
* drawable is used.
*
* @param width Width of the divider
*/
public void setDividerWidth(int width) {
mDividerWidth = width;
}
/**
* Returns the current number of segment in this SegmentBar
*
* @return The number of segments in this SegmentBar
*/
public int getSegmentCount() {
int segmentCount = getChildCount();
// If we have divider we'll have an odd number of child
if (mDividerDrawable != null) {
segmentCount = (segmentCount + 1) / 2;
}
return segmentCount;
}
/**
* Use this method to register an OnSegmentChangeListener and listen to
* changes that occur on this SegmentBar
*
* @param listener The listener to use
*/
public void setOnSegmentChangeListener(OnSegmentChangeListener listener) {
mOnSegmentChangeListener = listener;
}
/**
* Sets the current segment to the index <em>index</em>
*
* @param index The index of the segment to set. Client will be notified
* using this method of the segment change.
*/
public void setCurrentSegment(int index) {
if (index < 0 || index >= getSegmentCount()) {
return;
}
((CheckBox) getChildSegmentAt(mCheckedSegment)).setChecked(false);
mCheckedSegment = index;
((CheckBox) getChildSegmentAt(mCheckedSegment)).setChecked(true);
}
/**
* Returns the view representing the segment at the index <em>index</em>
*
* @param index The index of the segment to retrieve
* @return The view that represents the segment at index <em>index</em>
*/
public View getChildSegmentAt(int index) {
/*
* If we are using dividers, then instead of segments at 0, 1, 2, ... we
* have segments at 0, 2, 4, ...
*/
if (mDividerDrawable != null) {
index *= 2;
}
return getChildAt(index);
}
/**
* Adds a segment to the SegmentBar. This method automatically adds a
* divider if needed.
*
* @param title The title of the segment to add.
*/
public void addSegment(String title) {
final Context context = getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
/*
* First of all, we have to check whether or not we need to add a
* divider. A divider is added when there is at least one segment
*/
if (mDividerDrawable != null && getSegmentCount() > 0) {
ImageView divider = new ImageView(context);
final int width = (mDividerWidth > 0) ? mDividerWidth : mDividerDrawable.getIntrinsicWidth();
final LinearLayout.LayoutParams lp = new LayoutParams(width, LayoutParams.FILL_PARENT);
lp.setMargins(0, 0, 0, 0);
divider.setLayoutParams(lp);
divider.setBackgroundDrawable(mDividerDrawable);
addView(divider);
}
CheckBox segment = (CheckBox) inflater.inflate(R.layout.gd_segment, this, false);
segment.setText(title);
segment.setClickable(true);
segment.setFocusable(true);
segment.setOnFocusChangeListener(this);
segment.setOnCheckedChangeListener(new SegmentCheckedListener(getSegmentCount()));
segment.setOnClickListener(new SegmentClickedListener(getSegmentCount()));
addView(segment);
}
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
return;
}
if (v == this) {
final View segment = getChildSegmentAt(mCheckedSegment);
if (segment != null) {
segment.requestFocus();
}
}
else {
final int segmentCounts = getSegmentCount();
for (int i = 0; i < segmentCounts; i++) {
if (getChildSegmentAt(i) == v) {
setCurrentSegment(i);
notifyListener(i, false);
break;
}
}
}
}
private class SegmentClickedListener implements OnClickListener {
private final int mSegmentIndex;
private SegmentClickedListener(int segmentIndex) {
mSegmentIndex = segmentIndex;
}
public void onClick(View v) {
final CheckBox segment = (CheckBox) getChildSegmentAt(mCheckedSegment);
if (mSegmentIndex == mCheckedSegment && !segment.isChecked()) {
segment.setChecked(true);
} else {
notifyListener(mSegmentIndex, true);
}
}
}
private class SegmentCheckedListener implements OnCheckedChangeListener {
private final int mSegmentIndex;
private SegmentCheckedListener(int segmentIndex) {
mSegmentIndex = segmentIndex;
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
setCurrentSegment(mSegmentIndex);
}
}
}
private void notifyListener(int index, boolean clicked) {
if (mOnSegmentChangeListener != null) {
mOnSegmentChangeListener.onSegmentChange(index, clicked);
}
}
}