Java tutorial
/* * Copyright (C) 2008 The Android Open Source Project * * 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 com.androidinspain.deskclock.timer; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.PorterDuff; import android.support.annotation.IdRes; import android.support.v4.view.ViewCompat; import android.text.BidiFormatter; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.RelativeSizeSpan; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.androidinspain.deskclock.FabContainer; import com.androidinspain.deskclock.FormattedTextUtils; import com.androidinspain.deskclock.R; import com.androidinspain.deskclock.ThemeUtils; import com.androidinspain.deskclock.uidata.UiDataModel; import java.io.Serializable; import java.util.Arrays; public class TimerSetupView extends LinearLayout implements View.OnClickListener, View.OnLongClickListener { private final int[] mInput = { 0, 0, 0, 0, 0, 0 }; private int mInputPointer = -1; private CharSequence mTimeTemplate; private TextView mTimeView; private View mDeleteView; private View mDividerView; private TextView[] mDigitViews; /** Updates to the fab are requested via this container. */ private FabContainer mFabContainer; public TimerSetupView(Context context) { this(context, null /* attrs */); } public TimerSetupView(Context context, AttributeSet attrs) { super(context, attrs); final BidiFormatter bf = BidiFormatter.getInstance(false /* rtlContext */); final String hoursLabel = bf.unicodeWrap(context.getString(R.string.hours_label)); final String minutesLabel = bf.unicodeWrap(context.getString(R.string.minutes_label)); final String secondsLabel = bf.unicodeWrap(context.getString(R.string.seconds_label)); // Create a formatted template for "00h 00m 00s". mTimeTemplate = TextUtils.expandTemplate("^1^4 ^2^5 ^3^6", bf.unicodeWrap("^1"), bf.unicodeWrap("^2"), bf.unicodeWrap("^3"), FormattedTextUtils.formatText(hoursLabel, new RelativeSizeSpan(0.5f)), FormattedTextUtils.formatText(minutesLabel, new RelativeSizeSpan(0.5f)), FormattedTextUtils.formatText(secondsLabel, new RelativeSizeSpan(0.5f))); LayoutInflater.from(context).inflate(R.layout.timer_setup_container, this); } @Override protected void onFinishInflate() { super.onFinishInflate(); mTimeView = (TextView) findViewById(R.id.timer_setup_time); mDeleteView = findViewById(R.id.timer_setup_delete); mDividerView = findViewById(R.id.timer_setup_divider); mDigitViews = new TextView[] { (TextView) findViewById(R.id.timer_setup_digit_0), (TextView) findViewById(R.id.timer_setup_digit_1), (TextView) findViewById(R.id.timer_setup_digit_2), (TextView) findViewById(R.id.timer_setup_digit_3), (TextView) findViewById(R.id.timer_setup_digit_4), (TextView) findViewById(R.id.timer_setup_digit_5), (TextView) findViewById(R.id.timer_setup_digit_6), (TextView) findViewById(R.id.timer_setup_digit_7), (TextView) findViewById(R.id.timer_setup_digit_8), (TextView) findViewById(R.id.timer_setup_digit_9), }; // Tint the divider to match the disabled control color by default and used the activated // control color when there is valid input. final Context dividerContext = mDividerView.getContext(); final int colorControlActivated = ThemeUtils.resolveColor(dividerContext, R.attr.colorControlActivated); final int colorControlDisabled = ThemeUtils.resolveColor(dividerContext, R.attr.colorControlNormal, new int[] { ~android.R.attr.state_enabled }); ViewCompat.setBackgroundTintList(mDividerView, new ColorStateList(new int[][] { { android.R.attr.state_activated }, {} }, new int[] { colorControlActivated, colorControlDisabled })); ViewCompat.setBackgroundTintMode(mDividerView, PorterDuff.Mode.SRC); // Initialize the digit buttons. final UiDataModel uidm = UiDataModel.getUiDataModel(); for (final TextView digitView : mDigitViews) { final int digit = getDigitForId(digitView.getId()); digitView.setText(uidm.getFormattedNumber(digit, 1)); digitView.setOnClickListener(this); } mDeleteView.setOnClickListener(this); mDeleteView.setOnLongClickListener(this); updateTime(); updateDeleteAndDivider(); } public void setFabContainer(FabContainer fabContainer) { mFabContainer = fabContainer; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { View view = null; if (keyCode == KeyEvent.KEYCODE_DEL) { view = mDeleteView; } else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { view = mDigitViews[keyCode - KeyEvent.KEYCODE_0]; } if (view != null) { final boolean result = view.performClick(); if (result && hasValidInput()) { mFabContainer.updateFab(FabContainer.FAB_REQUEST_FOCUS); } return result; } return false; } @Override public void onClick(View view) { if (view == mDeleteView) { delete(); } else { append(getDigitForId(view.getId())); } } @Override public boolean onLongClick(View view) { if (view == mDeleteView) { reset(); updateFab(); return true; } return false; } private int getDigitForId(@IdRes int id) { switch (id) { case R.id.timer_setup_digit_0: return 0; case R.id.timer_setup_digit_1: return 1; case R.id.timer_setup_digit_2: return 2; case R.id.timer_setup_digit_3: return 3; case R.id.timer_setup_digit_4: return 4; case R.id.timer_setup_digit_5: return 5; case R.id.timer_setup_digit_6: return 6; case R.id.timer_setup_digit_7: return 7; case R.id.timer_setup_digit_8: return 8; case R.id.timer_setup_digit_9: return 9; } throw new IllegalArgumentException("Invalid id: " + id); } private void updateTime() { final int seconds = mInput[1] * 10 + mInput[0]; final int minutes = mInput[3] * 10 + mInput[2]; final int hours = mInput[5] * 10 + mInput[4]; final UiDataModel uidm = UiDataModel.getUiDataModel(); mTimeView.setText(TextUtils.expandTemplate(mTimeTemplate, uidm.getFormattedNumber(hours, 2), uidm.getFormattedNumber(minutes, 2), uidm.getFormattedNumber(seconds, 2))); final Resources r = getResources(); mTimeView.setContentDescription( r.getString(R.string.timer_setup_description, r.getQuantityString(R.plurals.hours, hours, hours), r.getQuantityString(R.plurals.minutes, minutes, minutes), r.getQuantityString(R.plurals.seconds, seconds, seconds))); } private void updateDeleteAndDivider() { final boolean enabled = hasValidInput(); mDeleteView.setEnabled(enabled); mDividerView.setActivated(enabled); } private void updateFab() { mFabContainer.updateFab(FabContainer.FAB_SHRINK_AND_EXPAND); } private void append(int digit) { if (digit < 0 || digit > 9) { throw new IllegalArgumentException("Invalid digit: " + digit); } // Pressing "0" as the first digit does nothing. if (mInputPointer == -1 && digit == 0) { return; } // No space for more digits, so ignore input. if (mInputPointer == mInput.length - 1) { return; } // Append the new digit. System.arraycopy(mInput, 0, mInput, 1, mInputPointer + 1); mInput[0] = digit; mInputPointer++; updateTime(); // Update TalkBack to read the number being deleted. mDeleteView.setContentDescription(getContext().getString(R.string.timer_descriptive_delete, UiDataModel.getUiDataModel().getFormattedNumber(digit))); // Update the fab, delete, and divider when we have valid input. if (mInputPointer == 0) { updateFab(); updateDeleteAndDivider(); } } private void delete() { // Nothing exists to delete so return. if (mInputPointer < 0) { return; } System.arraycopy(mInput, 1, mInput, 0, mInputPointer); mInput[mInputPointer] = 0; mInputPointer--; updateTime(); // Update TalkBack to read the number being deleted or its original description. if (mInputPointer >= 0) { mDeleteView.setContentDescription(getContext().getString(R.string.timer_descriptive_delete, UiDataModel.getUiDataModel().getFormattedNumber(mInput[0]))); } else { mDeleteView.setContentDescription(getContext().getString(R.string.timer_delete)); } // Update the fab, delete, and divider when we no longer have valid input. if (mInputPointer == -1) { updateFab(); updateDeleteAndDivider(); } } public void reset() { if (mInputPointer != -1) { Arrays.fill(mInput, 0); mInputPointer = -1; updateTime(); updateDeleteAndDivider(); } } public boolean hasValidInput() { return mInputPointer != -1; } public long getTimeInMillis() { final int seconds = mInput[1] * 10 + mInput[0]; final int minutes = mInput[3] * 10 + mInput[2]; final int hours = mInput[5] * 10 + mInput[4]; return seconds * DateUtils.SECOND_IN_MILLIS + minutes * DateUtils.MINUTE_IN_MILLIS + hours * DateUtils.HOUR_IN_MILLIS; } /** * @return an opaque representation of the state of timer setup */ public Serializable getState() { return Arrays.copyOf(mInput, mInput.length); } /** * @param state an opaque state of this view previously produced by {@link #getState()} */ public void setState(Serializable state) { final int[] input = (int[]) state; if (input != null && mInput.length == input.length) { for (int i = 0; i < mInput.length; i++) { mInput[i] = input[i]; if (mInput[i] != 0) { mInputPointer = i; } } updateTime(); updateDeleteAndDivider(); } } }