604 lines
23 KiB
Java
604 lines
23 KiB
Java
/*
|
|
* Copyright (C) 2009 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.android.email.activity;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.Configuration;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Typeface;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.text.Layout.Alignment;
|
|
import android.text.Spannable;
|
|
import android.text.SpannableString;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.text.StaticLayout;
|
|
import android.text.TextPaint;
|
|
import android.text.TextUtils;
|
|
import android.text.TextUtils.TruncateAt;
|
|
import android.text.format.DateUtils;
|
|
import android.text.style.ForegroundColorSpan;
|
|
import android.text.style.StyleSpan;
|
|
import android.util.AttributeSet;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
|
|
import com.android.email.R;
|
|
import com.android.emailcommon.utility.TextUtilities;
|
|
import com.google.common.base.Objects;
|
|
|
|
/**
|
|
* This custom View is the list item for the MessageList activity, and serves two purposes:
|
|
* 1. It's a container to store message metadata (e.g. the ids of the message, mailbox, & account)
|
|
* 2. It handles internal clicks such as the checkbox or the favorite star
|
|
*/
|
|
public class MessageListItem extends View {
|
|
// Note: messagesAdapter directly fiddles with these fields.
|
|
/* package */ long mMessageId;
|
|
/* package */ long mMailboxId;
|
|
/* package */ long mAccountId;
|
|
|
|
private ThreePaneLayout mLayout;
|
|
private MessagesAdapter mAdapter;
|
|
private MessageListItemCoordinates mCoordinates;
|
|
private Context mContext;
|
|
private boolean mIsSearchResult = false;
|
|
|
|
private boolean mDownEvent;
|
|
|
|
public static final String MESSAGE_LIST_ITEMS_CLIP_LABEL =
|
|
"com.android.email.MESSAGE_LIST_ITEMS";
|
|
|
|
public MessageListItem(Context context) {
|
|
super(context);
|
|
init(context);
|
|
}
|
|
|
|
public MessageListItem(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
init(context);
|
|
}
|
|
|
|
public MessageListItem(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
init(context);
|
|
}
|
|
|
|
// Wide mode shows sender, snippet, time, and favorite spread out across the screen
|
|
private static final int MODE_WIDE = MessageListItemCoordinates.WIDE_MODE;
|
|
// Sentinel indicating that the view needs layout
|
|
public static final int NEEDS_LAYOUT = -1;
|
|
|
|
private static boolean sInit = false;
|
|
private static final TextPaint sDefaultPaint = new TextPaint();
|
|
private static final TextPaint sBoldPaint = new TextPaint();
|
|
private static final TextPaint sDatePaint = new TextPaint();
|
|
private static Bitmap sAttachmentIcon;
|
|
private static Bitmap sInviteIcon;
|
|
private static int sBadgeMargin;
|
|
private static Bitmap sFavoriteIconOff;
|
|
private static Bitmap sFavoriteIconOn;
|
|
private static Bitmap sSelectedIconOn;
|
|
private static Bitmap sSelectedIconOff;
|
|
private static Bitmap sStateReplied;
|
|
private static Bitmap sStateForwarded;
|
|
private static Bitmap sStateRepliedAndForwarded;
|
|
private static String sSubjectSnippetDivider;
|
|
private static String sSubjectDescription;
|
|
private static String sSubjectEmptyDescription;
|
|
|
|
// Static colors.
|
|
private static int DEFAULT_TEXT_COLOR;
|
|
private static int ACTIVATED_TEXT_COLOR;
|
|
private static int LIGHT_TEXT_COLOR;
|
|
private static int DRAFT_TEXT_COLOR;
|
|
private static int SUBJECT_TEXT_COLOR_READ;
|
|
private static int SUBJECT_TEXT_COLOR_UNREAD;
|
|
private static int SNIPPET_TEXT_COLOR_READ;
|
|
private static int SNIPPET_TEXT_COLOR_UNREAD;
|
|
private static int SENDERS_TEXT_COLOR_READ;
|
|
private static int SENDERS_TEXT_COLOR_UNREAD;
|
|
private static int DATE_TEXT_COLOR_READ;
|
|
private static int DATE_TEXT_COLOR_UNREAD;
|
|
|
|
public String mSender;
|
|
public SpannableStringBuilder mText;
|
|
public CharSequence mSnippet;
|
|
private String mSubject;
|
|
private StaticLayout mSubjectLayout;
|
|
public boolean mRead;
|
|
public boolean mHasAttachment = false;
|
|
public boolean mHasInvite = true;
|
|
public boolean mIsFavorite = false;
|
|
public boolean mHasBeenRepliedTo = false;
|
|
public boolean mHasBeenForwarded = false;
|
|
/** {@link Paint} for account color chips. null if no chips should be drawn. */
|
|
public Paint mColorChipPaint;
|
|
|
|
private int mMode = -1;
|
|
|
|
private int mViewWidth = 0;
|
|
private int mViewHeight = 0;
|
|
|
|
private static int sItemHeightWide;
|
|
private static int sItemHeightNormal;
|
|
|
|
// Note: these cannot be shared Drawables because they are selectors which have state.
|
|
private Drawable mReadSelector;
|
|
private Drawable mUnreadSelector;
|
|
private Drawable mWideReadSelector;
|
|
private Drawable mWideUnreadSelector;
|
|
|
|
private CharSequence mFormattedSender;
|
|
// We must initialize this to something, in case the timestamp of the message is zero (which
|
|
// should be very rare); this is otherwise set in setTimestamp
|
|
private CharSequence mFormattedDate = "";
|
|
|
|
private void init(Context context) {
|
|
mContext = context;
|
|
if (!sInit) {
|
|
Resources r = context.getResources();
|
|
sSubjectDescription = r.getString(R.string.message_subject_description).concat(", ");
|
|
sSubjectEmptyDescription = r.getString(R.string.message_is_empty_description);
|
|
sSubjectSnippetDivider = r.getString(R.string.message_list_subject_snippet_divider);
|
|
sItemHeightWide =
|
|
r.getDimensionPixelSize(R.dimen.message_list_item_height_wide);
|
|
sItemHeightNormal =
|
|
r.getDimensionPixelSize(R.dimen.message_list_item_height_normal);
|
|
|
|
sDefaultPaint.setTypeface(Typeface.DEFAULT);
|
|
sDefaultPaint.setAntiAlias(true);
|
|
sDatePaint.setTypeface(Typeface.DEFAULT);
|
|
sDatePaint.setAntiAlias(true);
|
|
sBoldPaint.setTypeface(Typeface.DEFAULT_BOLD);
|
|
sBoldPaint.setAntiAlias(true);
|
|
|
|
sAttachmentIcon = BitmapFactory.decodeResource(r, R.drawable.ic_badge_attachment);
|
|
sInviteIcon = BitmapFactory.decodeResource(r, R.drawable.ic_badge_invite_holo_light);
|
|
sBadgeMargin = r.getDimensionPixelSize(R.dimen.message_list_badge_margin);
|
|
sFavoriteIconOff =
|
|
BitmapFactory.decodeResource(r, R.drawable.btn_star_off_normal_email_holo_light);
|
|
sFavoriteIconOn =
|
|
BitmapFactory.decodeResource(r, R.drawable.btn_star_on_normal_email_holo_light);
|
|
sSelectedIconOff =
|
|
BitmapFactory.decodeResource(r, R.drawable.btn_check_off_normal_holo_light);
|
|
sSelectedIconOn =
|
|
BitmapFactory.decodeResource(r, R.drawable.btn_check_on_normal_holo_light);
|
|
|
|
sStateReplied =
|
|
BitmapFactory.decodeResource(r, R.drawable.ic_badge_reply_holo_light);
|
|
sStateForwarded =
|
|
BitmapFactory.decodeResource(r, R.drawable.ic_badge_forward_holo_light);
|
|
sStateRepliedAndForwarded =
|
|
BitmapFactory.decodeResource(r, R.drawable.ic_badge_reply_forward_holo_light);
|
|
|
|
DEFAULT_TEXT_COLOR = r.getColor(R.color.default_text_color);
|
|
ACTIVATED_TEXT_COLOR = r.getColor(android.R.color.white);
|
|
SUBJECT_TEXT_COLOR_READ = r.getColor(R.color.subject_text_color_read);
|
|
SUBJECT_TEXT_COLOR_UNREAD = r.getColor(R.color.subject_text_color_unread);
|
|
SNIPPET_TEXT_COLOR_READ = r.getColor(R.color.snippet_text_color_read);
|
|
SNIPPET_TEXT_COLOR_UNREAD = r.getColor(R.color.snippet_text_color_unread);
|
|
SENDERS_TEXT_COLOR_READ = r.getColor(R.color.senders_text_color_read);
|
|
SENDERS_TEXT_COLOR_UNREAD = r.getColor(R.color.senders_text_color_unread);
|
|
DATE_TEXT_COLOR_READ = r.getColor(R.color.date_text_color_read);
|
|
DATE_TEXT_COLOR_UNREAD = r.getColor(R.color.date_text_color_unread);
|
|
|
|
sInit = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate all drawing caches associated with drawing message list items.
|
|
* This is an expensive operation, and should be done rarely, such as when system font size
|
|
* changes occurs.
|
|
*/
|
|
public static void resetDrawingCaches() {
|
|
MessageListItemCoordinates.resetCaches();
|
|
sInit = false;
|
|
}
|
|
|
|
/**
|
|
* Sets message subject and snippet safely, ensuring the cache is invalidated.
|
|
*/
|
|
public void setText(String subject, String snippet, boolean forceUpdate) {
|
|
boolean changed = false;
|
|
if (!Objects.equal(mSubject, subject)) {
|
|
mSubject = subject;
|
|
changed = true;
|
|
populateContentDescription();
|
|
}
|
|
|
|
if (!Objects.equal(mSnippet, snippet)) {
|
|
mSnippet = snippet;
|
|
changed = true;
|
|
}
|
|
|
|
if (forceUpdate || changed || (mSubject == null && mSnippet == null) /* first time */) {
|
|
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
|
boolean hasSubject = false;
|
|
if (!TextUtils.isEmpty(mSubject)) {
|
|
SpannableString ss = new SpannableString(mSubject);
|
|
ss.setSpan(new StyleSpan(mRead ? Typeface.NORMAL : Typeface.BOLD), 0, ss.length(),
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
ssb.append(ss);
|
|
hasSubject = true;
|
|
}
|
|
if (!TextUtils.isEmpty(mSnippet)) {
|
|
if (hasSubject) {
|
|
ssb.append(sSubjectSnippetDivider);
|
|
}
|
|
ssb.append(mSnippet);
|
|
}
|
|
mText = ssb;
|
|
requestLayout();
|
|
}
|
|
}
|
|
|
|
long mTimeFormatted = 0;
|
|
|
|
public void setTimestamp(long timestamp) {
|
|
if (mTimeFormatted != timestamp) {
|
|
mFormattedDate = DateUtils.getRelativeTimeSpanString(mContext, timestamp).toString();
|
|
mTimeFormatted = timestamp;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine the mode of this view (WIDE or NORMAL)
|
|
*
|
|
* @param width The width of the view
|
|
* @return The mode of the view
|
|
*/
|
|
private int getViewMode(int width) {
|
|
return MessageListItemCoordinates.getMode(mContext, width, mIsSearchResult);
|
|
}
|
|
|
|
private Drawable mCurentBackground = null; // Only used by updateBackground()
|
|
|
|
private void updateBackground() {
|
|
final Drawable newBackground;
|
|
boolean isMultiPane = MessageListItemCoordinates.isMultiPane(mContext);
|
|
if (mRead) {
|
|
if (isMultiPane && mLayout.isLeftPaneVisible()) {
|
|
if (mWideReadSelector == null) {
|
|
mWideReadSelector = getContext().getResources()
|
|
.getDrawable(R.drawable.conversation_wide_read_selector);
|
|
}
|
|
newBackground = mWideReadSelector;
|
|
} else {
|
|
if (mReadSelector == null) {
|
|
mReadSelector = getContext().getResources()
|
|
.getDrawable(R.drawable.conversation_read_selector);
|
|
}
|
|
newBackground = mReadSelector;
|
|
}
|
|
} else {
|
|
if (isMultiPane && mLayout.isLeftPaneVisible()) {
|
|
if (mWideUnreadSelector == null) {
|
|
mWideUnreadSelector = getContext().getResources().getDrawable(
|
|
R.drawable.conversation_wide_unread_selector);
|
|
}
|
|
newBackground = mWideUnreadSelector;
|
|
} else {
|
|
if (mUnreadSelector == null) {
|
|
mUnreadSelector = getContext().getResources()
|
|
.getDrawable(R.drawable.conversation_unread_selector);
|
|
}
|
|
newBackground = mUnreadSelector;
|
|
}
|
|
}
|
|
if (newBackground != mCurentBackground) {
|
|
// setBackgroundDrawable is a heavy operation. Only call it when really needed.
|
|
setBackgroundDrawable(newBackground);
|
|
mCurentBackground = newBackground;
|
|
}
|
|
}
|
|
|
|
private void calculateSubjectText() {
|
|
if (mText == null || mText.length() == 0) {
|
|
return;
|
|
}
|
|
boolean hasSubject = false;
|
|
int snippetStart = 0;
|
|
if (!TextUtils.isEmpty(mSubject)) {
|
|
int subjectColor = getFontColor(mRead ? SUBJECT_TEXT_COLOR_READ
|
|
: SUBJECT_TEXT_COLOR_UNREAD);
|
|
mText.setSpan(new ForegroundColorSpan(subjectColor), 0, mSubject.length(),
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
snippetStart = mSubject.length() + 1;
|
|
}
|
|
if (!TextUtils.isEmpty(mSnippet)) {
|
|
int snippetColor = getFontColor(mRead ? SNIPPET_TEXT_COLOR_READ
|
|
: SNIPPET_TEXT_COLOR_UNREAD);
|
|
mText.setSpan(new ForegroundColorSpan(snippetColor), snippetStart, mText.length(),
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
}
|
|
|
|
private void calculateDrawingData() {
|
|
sDefaultPaint.setTextSize(mCoordinates.subjectFontSize);
|
|
calculateSubjectText();
|
|
mSubjectLayout = new StaticLayout(mText, sDefaultPaint,
|
|
mCoordinates.subjectWidth, Alignment.ALIGN_NORMAL, 1, 0, false /* includePad */);
|
|
if (mCoordinates.subjectLineCount < mSubjectLayout.getLineCount()) {
|
|
// TODO: ellipsize.
|
|
int end = mSubjectLayout.getLineEnd(mCoordinates.subjectLineCount - 1);
|
|
mSubjectLayout = new StaticLayout(mText.subSequence(0, end),
|
|
sDefaultPaint, mCoordinates.subjectWidth, Alignment.ALIGN_NORMAL, 1, 0, true);
|
|
}
|
|
|
|
// Now, format the sender for its width
|
|
TextPaint senderPaint = mRead ? sDefaultPaint : sBoldPaint;
|
|
// And get the ellipsized string for the calculated width
|
|
if (TextUtils.isEmpty(mSender)) {
|
|
mFormattedSender = "";
|
|
} else {
|
|
int senderWidth = mCoordinates.sendersWidth;
|
|
senderPaint.setTextSize(mCoordinates.sendersFontSize);
|
|
senderPaint.setColor(getFontColor(mRead ? SENDERS_TEXT_COLOR_READ
|
|
: SENDERS_TEXT_COLOR_UNREAD));
|
|
mFormattedSender = TextUtils.ellipsize(mSender, senderPaint, senderWidth,
|
|
TruncateAt.END);
|
|
}
|
|
}
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
if (widthMeasureSpec != 0 || mViewWidth == 0) {
|
|
mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
|
|
int mode = getViewMode(mViewWidth);
|
|
if (mode != mMode) {
|
|
mMode = mode;
|
|
}
|
|
mViewHeight = measureHeight(heightMeasureSpec, mMode);
|
|
}
|
|
setMeasuredDimension(mViewWidth, mViewHeight);
|
|
}
|
|
|
|
/**
|
|
* Determine the height of this view
|
|
*
|
|
* @param measureSpec A measureSpec packed into an int
|
|
* @param mode The current mode of this view
|
|
* @return The height of the view, honoring constraints from measureSpec
|
|
*/
|
|
private int measureHeight(int measureSpec, int mode) {
|
|
int result = 0;
|
|
int specMode = MeasureSpec.getMode(measureSpec);
|
|
int specSize = MeasureSpec.getSize(measureSpec);
|
|
|
|
if (specMode == MeasureSpec.EXACTLY) {
|
|
// We were told how big to be
|
|
result = specSize;
|
|
} else {
|
|
// Measure the text
|
|
if (mMode == MODE_WIDE) {
|
|
result = sItemHeightWide;
|
|
} else {
|
|
result = sItemHeightNormal;
|
|
}
|
|
if (specMode == MeasureSpec.AT_MOST) {
|
|
// Respect AT_MOST value if that was what is called for by
|
|
// measureSpec
|
|
result = Math.min(result, specSize);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
// Update the background, before View.draw() draws it.
|
|
setSelected(mAdapter.isSelected(this));
|
|
updateBackground();
|
|
super.draw(canvas);
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
super.onLayout(changed, left, top, right, bottom);
|
|
|
|
mCoordinates = MessageListItemCoordinates.forWidth(mContext, mViewWidth, mIsSearchResult);
|
|
calculateDrawingData();
|
|
}
|
|
|
|
private int getFontColor(int defaultColor) {
|
|
return isActivated() && MessageListItemCoordinates.isMultiPane(mContext) ?
|
|
ACTIVATED_TEXT_COLOR : defaultColor;
|
|
}
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas) {
|
|
// Draw the color chip indicating the mailbox this belongs to
|
|
if (mColorChipPaint != null) {
|
|
canvas.drawRect(
|
|
mCoordinates.chipX, mCoordinates.chipY,
|
|
mCoordinates.chipX + mCoordinates.chipWidth,
|
|
mCoordinates.chipY + mCoordinates.chipHeight,
|
|
mColorChipPaint);
|
|
}
|
|
|
|
// Draw the checkbox
|
|
canvas.drawBitmap(mAdapter.isSelected(this) ? sSelectedIconOn : sSelectedIconOff,
|
|
mCoordinates.checkmarkX, mCoordinates.checkmarkY, null);
|
|
|
|
// Draw the sender name
|
|
Paint senderPaint = mRead ? sDefaultPaint : sBoldPaint;
|
|
senderPaint.setColor(getFontColor(mRead ? SENDERS_TEXT_COLOR_READ
|
|
: SENDERS_TEXT_COLOR_UNREAD));
|
|
senderPaint.setTextSize(mCoordinates.sendersFontSize);
|
|
canvas.drawText(mFormattedSender, 0, mFormattedSender.length(),
|
|
mCoordinates.sendersX, mCoordinates.sendersY - mCoordinates.sendersAscent,
|
|
senderPaint);
|
|
|
|
// Draw the reply state. Draw nothing if neither replied nor forwarded.
|
|
if (mHasBeenRepliedTo && mHasBeenForwarded) {
|
|
canvas.drawBitmap(sStateRepliedAndForwarded,
|
|
mCoordinates.stateX, mCoordinates.stateY, null);
|
|
} else if (mHasBeenRepliedTo) {
|
|
canvas.drawBitmap(sStateReplied,
|
|
mCoordinates.stateX, mCoordinates.stateY, null);
|
|
} else if (mHasBeenForwarded) {
|
|
canvas.drawBitmap(sStateForwarded,
|
|
mCoordinates.stateX, mCoordinates.stateY, null);
|
|
}
|
|
|
|
// Subject and snippet.
|
|
sDefaultPaint.setTextSize(mCoordinates.subjectFontSize);
|
|
canvas.save();
|
|
canvas.translate(
|
|
mCoordinates.subjectX,
|
|
mCoordinates.subjectY);
|
|
mSubjectLayout.draw(canvas);
|
|
canvas.restore();
|
|
|
|
// Draw the date
|
|
sDatePaint.setTextSize(mCoordinates.dateFontSize);
|
|
sDatePaint.setColor(mRead ? DATE_TEXT_COLOR_READ : DATE_TEXT_COLOR_UNREAD);
|
|
int dateX = mCoordinates.dateXEnd
|
|
- (int) sDatePaint.measureText(mFormattedDate, 0, mFormattedDate.length());
|
|
|
|
canvas.drawText(mFormattedDate, 0, mFormattedDate.length(),
|
|
dateX, mCoordinates.dateY - mCoordinates.dateAscent, sDatePaint);
|
|
|
|
// Draw the favorite icon
|
|
canvas.drawBitmap(mIsFavorite ? sFavoriteIconOn : sFavoriteIconOff,
|
|
mCoordinates.starX, mCoordinates.starY, null);
|
|
|
|
// TODO: deal with the icon layouts better from the coordinate class so that this logic
|
|
// doesn't have to exist.
|
|
// Draw the attachment and invite icons, if necessary.
|
|
int iconsLeft = dateX - sBadgeMargin;
|
|
if (mHasAttachment) {
|
|
iconsLeft = iconsLeft - sAttachmentIcon.getWidth();
|
|
canvas.drawBitmap(sAttachmentIcon, iconsLeft, mCoordinates.paperclipY, null);
|
|
}
|
|
if (mHasInvite) {
|
|
iconsLeft -= sInviteIcon.getWidth();
|
|
canvas.drawBitmap(sInviteIcon, iconsLeft, mCoordinates.paperclipY, null);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Called by the adapter at bindView() time
|
|
*
|
|
* @param adapter the adapter that creates this view
|
|
* @param layout If this is a three pane implementation, the
|
|
* ThreePaneLayout. Otherwise, null.
|
|
*/
|
|
public void bindViewInit(MessagesAdapter adapter, ThreePaneLayout layout,
|
|
boolean isSearchResult) {
|
|
mLayout = layout;
|
|
mAdapter = adapter;
|
|
mIsSearchResult = isSearchResult;
|
|
requestLayout();
|
|
}
|
|
|
|
private static final int TOUCH_SLOP = 24;
|
|
private static int sScaledTouchSlop = -1;
|
|
|
|
private void initializeSlop(Context context) {
|
|
if (sScaledTouchSlop == -1) {
|
|
final Resources res = context.getResources();
|
|
final Configuration config = res.getConfiguration();
|
|
final float density = res.getDisplayMetrics().density;
|
|
final float sizeAndDensity;
|
|
if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) {
|
|
sizeAndDensity = density * 1.5f;
|
|
} else {
|
|
sizeAndDensity = density;
|
|
}
|
|
sScaledTouchSlop = (int) (sizeAndDensity * TOUCH_SLOP + 0.5f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overriding this method allows us to "catch" clicks in the checkbox or star
|
|
* and process them accordingly.
|
|
*/
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
initializeSlop(getContext());
|
|
|
|
boolean handled = false;
|
|
int touchX = (int) event.getX();
|
|
int checkRight = mCoordinates.checkmarkX
|
|
+ mCoordinates.checkmarkWidthIncludingMargins + sScaledTouchSlop;
|
|
int starLeft = mCoordinates.starX - sScaledTouchSlop;
|
|
|
|
switch (event.getAction()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
if (touchX < checkRight || touchX > starLeft) {
|
|
mDownEvent = true;
|
|
if ((touchX < checkRight) || (touchX > starLeft)) {
|
|
handled = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MotionEvent.ACTION_CANCEL:
|
|
mDownEvent = false;
|
|
break;
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
if (mDownEvent) {
|
|
if (touchX < checkRight) {
|
|
mAdapter.toggleSelected(this);
|
|
handled = true;
|
|
} else if (touchX > starLeft) {
|
|
mIsFavorite = !mIsFavorite;
|
|
mAdapter.updateFavorite(this, mIsFavorite);
|
|
handled = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (handled) {
|
|
invalidate();
|
|
} else {
|
|
handled = super.onTouchEvent(event);
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
|
event.setClassName(getClass().getName());
|
|
event.setPackageName(getContext().getPackageName());
|
|
event.setEnabled(true);
|
|
event.setContentDescription(getContentDescription());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sets the content description for this item, used for accessibility.
|
|
*/
|
|
private void populateContentDescription() {
|
|
if (!TextUtils.isEmpty(mSubject)) {
|
|
setContentDescription(sSubjectDescription + mSubject);
|
|
} else {
|
|
setContentDescription(sSubjectEmptyDescription);
|
|
}
|
|
}
|
|
}
|