diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java index 31000b29..7017c27d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java @@ -41,7 +41,6 @@ import android.widget.ImageButton; import android.widget.ScrollView; import android.widget.TextView; -import com.android.volley.toolbox.NetworkImageView; import org.apache.commons.io.IOUtils; import org.json.JSONArray; @@ -70,6 +69,7 @@ import jp.juggler.subwaytooter.util.ActionsDialog; import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.MyEditText; +import jp.juggler.subwaytooter.util.MyNetworkImageView; import jp.juggler.subwaytooter.util.PostAttachment; import jp.juggler.subwaytooter.util.Utils; import okhttp3.MediaType; @@ -496,7 +496,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, View btnAttachment; View btnPost; View llAttachment; - final NetworkImageView[] ivMedia = new NetworkImageView[ 4 ]; + final MyNetworkImageView[] ivMedia = new MyNetworkImageView[ 4 ]; CheckBox cbNSFW; CheckBox cbContentWarning; MyEditText etContentWarning; @@ -511,7 +511,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, View llReply; TextView tvReplyTo; View btnRemoveReply; - NetworkImageView ivReply; + MyNetworkImageView ivReply; ScrollView scrollView; private void initUI(){ @@ -527,10 +527,10 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, btnAttachment = findViewById( R.id.btnAttachment ); btnPost = findViewById( R.id.btnPost ); llAttachment = findViewById( R.id.llAttachment ); - ivMedia[ 0 ] = (NetworkImageView) findViewById( R.id.ivMedia1 ); - ivMedia[ 1 ] = (NetworkImageView) findViewById( R.id.ivMedia2 ); - ivMedia[ 2 ] = (NetworkImageView) findViewById( R.id.ivMedia3 ); - ivMedia[ 3 ] = (NetworkImageView) findViewById( R.id.ivMedia4 ); + ivMedia[ 0 ] = (MyNetworkImageView) findViewById( R.id.ivMedia1 ); + ivMedia[ 1 ] = (MyNetworkImageView) findViewById( R.id.ivMedia2 ); + ivMedia[ 2 ] = (MyNetworkImageView) findViewById( R.id.ivMedia3 ); + ivMedia[ 3 ] = (MyNetworkImageView) findViewById( R.id.ivMedia4 ); cbNSFW = (CheckBox) findViewById( R.id.cbNSFW ); cbContentWarning = (CheckBox) findViewById( R.id.cbContentWarning ); etContentWarning = (MyEditText) findViewById( R.id.etContentWarning ); @@ -540,7 +540,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, llReply = findViewById( R.id.llReply ); tvReplyTo = (TextView) findViewById( R.id.tvReplyTo ); btnRemoveReply = findViewById( R.id.btnRemoveReply ); - ivReply = (NetworkImageView) findViewById( R.id.ivReply ); + ivReply = (MyNetworkImageView) findViewById( R.id.ivReply ); account_list = SavedAccount.loadAccountList( log ); Collections.sort( account_list, new Comparator< SavedAccount >() { @@ -557,7 +557,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, btnPost.setOnClickListener( this ); btnRemoveReply.setOnClickListener( this ); - for( NetworkImageView iv : ivMedia ){ + for( MyNetworkImageView iv : ivMedia ){ iv.setOnClickListener( this ); iv.setDefaultImageResId( Styler.getAttributeResourceId( this, R.attr.ic_loading ) ); iv.setErrorImageResId( Styler.getAttributeResourceId( this, R.attr.ic_unknown ) ); @@ -776,11 +776,12 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, } } - private void showAttachment_sub( NetworkImageView iv, int idx ){ + private void showAttachment_sub( MyNetworkImageView iv, int idx ){ if( idx >= attachment_list.size() ){ iv.setVisibility( View.GONE ); }else{ iv.setVisibility( View.VISIBLE ); + iv.setCornerRadius( density * 4f ); PostAttachment a = attachment_list.get( idx ); if( a.attachment != null && a.status == PostAttachment.ATTACHMENT_UPLOADED ){ iv.setImageUrl( a.attachment.preview_url, App1.getImageLoader() ); @@ -1578,7 +1579,9 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, }else{ llReply.setVisibility( View.VISIBLE ); tvReplyTo.setText( HTMLDecoder.decodeHTML( account, in_reply_to_text ) ); + ivReply.setCornerRadius( density * 4f ); ivReply.setImageUrl( in_reply_to_image, App1.getImageLoader() ); + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolder.java index 9841b571..024051d1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolder.java @@ -7,7 +7,6 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import com.android.volley.toolbox.NetworkImageView; import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootStatus; @@ -15,6 +14,7 @@ import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.util.Emojione; import jp.juggler.subwaytooter.util.MyLinkMovementMethod; +import jp.juggler.subwaytooter.util.MyNetworkImageView; class HeaderViewHolder implements View.OnClickListener { private final Column column; @@ -22,9 +22,9 @@ class HeaderViewHolder implements View.OnClickListener { private final SavedAccount access_info; final View viewRoot; - private final NetworkImageView ivBackground; + private final MyNetworkImageView ivBackground; private final TextView tvCreated; - private final NetworkImageView ivAvatar; + private final MyNetworkImageView ivAvatar; private final TextView tvDisplayName; private final TextView tvAcct; private final Button btnFollowing; @@ -42,9 +42,9 @@ class HeaderViewHolder implements View.OnClickListener { this.access_info = column.access_info; this.viewRoot = activity.getLayoutInflater().inflate( R.layout.lv_list_header, parent, false ); - ivBackground = (NetworkImageView) viewRoot.findViewById( R.id.ivBackground ); + ivBackground = (MyNetworkImageView) viewRoot.findViewById( R.id.ivBackground ); tvCreated = (TextView) viewRoot.findViewById( R.id.tvCreated ); - ivAvatar = (NetworkImageView) viewRoot.findViewById( R.id.ivAvatar ); + ivAvatar = (MyNetworkImageView) viewRoot.findViewById( R.id.ivAvatar ); tvDisplayName = (TextView) viewRoot.findViewById( R.id.tvDisplayName ); tvAcct = (TextView) viewRoot.findViewById( R.id.tvAcct ); btnFollowing = (Button) viewRoot.findViewById( R.id.btnFollowing ); @@ -82,6 +82,7 @@ class HeaderViewHolder implements View.OnClickListener { }else{ tvCreated.setText( TootStatus.formatTime( who.time_created_at ) ); ivBackground.setImageUrl( access_info.supplyBaseUrl( who.header_static ), App1.getImageLoader() ); + ivAvatar.setCornerRadius( activity.density * 8f ); ivAvatar.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ), App1.getImageLoader() ); tvDisplayName.setText( who.display_name ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java index 4ff27a82..f74dfd22 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java @@ -8,8 +8,6 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import com.android.volley.toolbox.NetworkImageView; - import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootAttachment; import jp.juggler.subwaytooter.api.entity.TootGap; @@ -22,6 +20,7 @@ import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.util.MyLinkMovementMethod; import jp.juggler.subwaytooter.util.MyListView; +import jp.juggler.subwaytooter.util.MyNetworkImageView; import jp.juggler.subwaytooter.util.MyTextView; import jp.juggler.subwaytooter.util.Utils; @@ -39,14 +38,14 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { private final TextView tvBoostedTime; private final View llFollow; - private final NetworkImageView ivFollow; + private final MyNetworkImageView ivFollow; private final TextView tvFollowerName; private final TextView tvFollowerAcct; private final ImageButton btnFollow; private final ImageView ivFollowedBy; private final View llStatus; - private final NetworkImageView ivThumbnail; + private final MyNetworkImageView ivThumbnail; private final TextView tvName; private final TextView tvTime; private final TextView tvAcct; @@ -62,10 +61,10 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { private final View flMedia; private final View btnShowMedia; - private final NetworkImageView ivMedia1; - private final NetworkImageView ivMedia2; - private final NetworkImageView ivMedia3; - private final NetworkImageView ivMedia4; + private final MyNetworkImageView ivMedia1; + private final MyNetworkImageView ivMedia2; + private final MyNetworkImageView ivMedia3; + private final MyNetworkImageView ivMedia4; private final StatusButtons buttons_for_status; @@ -95,7 +94,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { this.tvBoostedAcct = (TextView) view.findViewById( R.id.tvBoostedAcct ); this.llFollow = view.findViewById( R.id.llFollow ); - this.ivFollow = (NetworkImageView) view.findViewById( R.id.ivFollow ); + this.ivFollow = (MyNetworkImageView) view.findViewById( R.id.ivFollow ); this.tvFollowerName = (TextView) view.findViewById( R.id.tvFollowerName ); this.tvFollowerAcct = (TextView) view.findViewById( R.id.tvFollowerAcct ); this.btnFollow = (ImageButton) view.findViewById( R.id.btnFollow ); @@ -103,7 +102,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { this.llStatus = view.findViewById( R.id.llStatus ); - this.ivThumbnail = (NetworkImageView) view.findViewById( R.id.ivThumbnail ); + this.ivThumbnail = (MyNetworkImageView) view.findViewById( R.id.ivThumbnail ); this.tvName = (TextView) view.findViewById( R.id.tvName ); this.tvTime = (TextView) view.findViewById( R.id.tvTime ); this.tvAcct = (TextView) view.findViewById( R.id.tvAcct ); @@ -120,10 +119,10 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { this.flMedia = view.findViewById( R.id.flMedia ); this.btnShowMedia = view.findViewById( R.id.btnShowMedia ); - this.ivMedia1 = (NetworkImageView) view.findViewById( R.id.ivMedia1 ); - this.ivMedia2 = (NetworkImageView) view.findViewById( R.id.ivMedia2 ); - this.ivMedia3 = (NetworkImageView) view.findViewById( R.id.ivMedia3 ); - this.ivMedia4 = (NetworkImageView) view.findViewById( R.id.ivMedia4 ); + this.ivMedia1 = (MyNetworkImageView) view.findViewById( R.id.ivMedia1 ); + this.ivMedia2 = (MyNetworkImageView) view.findViewById( R.id.ivMedia2 ); + this.ivMedia3 = (MyNetworkImageView) view.findViewById( R.id.ivMedia3 ); + this.ivMedia4 = (MyNetworkImageView) view.findViewById( R.id.ivMedia4 ); this.llSearchTag = view.findViewById( R.id.llSearchTag ); this.btnSearchTag = (Button) view.findViewById( R.id.btnSearchTag ); @@ -261,6 +260,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { private void showFollow( TootAccount who ){ account_follow = who; llFollow.setVisibility( View.VISIBLE ); + ivFollow.setCornerRadius( activity.density * 4f ); ivFollow.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ), App1.getImageLoader() ); tvFollowerName.setText( who.display_name ); setAcct( tvFollowerAcct, access_info.getFullAcct( who ), R.attr.colorAcctSmall ); @@ -278,6 +278,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { tvTime.setText( TootStatus.formatTime( status.time_created_at ) ); tvName.setText( status.account.display_name ); + ivThumbnail.setCornerRadius( activity.density * 4f ); ivThumbnail.setImageUrl( access_info.supplyBaseUrl( status.account.avatar_static ), App1.getImageLoader() ); tvContent.setText( status.decoded_content ); @@ -363,7 +364,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { } - private void setMedia( NetworkImageView iv, TootStatus status, int idx ){ + private void setMedia( MyNetworkImageView iv, TootStatus status, int idx ){ if( idx >= status.media_attachments.size() ){ iv.setVisibility( View.GONE ); }else{ @@ -371,6 +372,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { TootAttachment ta = status.media_attachments.get( idx ); String url = ta.preview_url; if( TextUtils.isEmpty( url ) ) url = ta.remote_url; + iv.setCornerRadius( 0f ); // 正方形じゃないせいか、うまく動かない activity.density * 4f ); iv.setImageUrl( access_info.supplyBaseUrl( url ), App1.getImageLoader() ); } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MyNetworkImageView.java b/app/src/main/java/jp/juggler/subwaytooter/util/MyNetworkImageView.java new file mode 100644 index 00000000..97dc0e95 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MyNetworkImageView.java @@ -0,0 +1,222 @@ +package jp.juggler.subwaytooter.util; + +import android.content.Context; +import android.support.v4.graphics.drawable.RoundedBitmapDrawable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.support.v7.widget.AppCompatImageView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; + +public class MyNetworkImageView extends AppCompatImageView { + /** + * The URL of the network image to load + */ + private String mUrl; + + /** + * Resource ID of the image to be used as a placeholder until the network image is loaded. + */ + private int mDefaultImageId; + + /** + * Resource ID of the image to be used if the network response fails. + */ + private int mErrorImageId; + + /** + * Local copy of the ImageLoader. + */ + private ImageLoader mImageLoader; + + /** + * Current ImageContainer. (either in-flight or finished) + */ + private ImageLoader.ImageContainer mImageContainer; + + public MyNetworkImageView( Context context ){ + this( context, null ); + } + + public MyNetworkImageView( Context context, AttributeSet attrs ){ + this( context, attrs, 0 ); + } + + public MyNetworkImageView( Context context, AttributeSet attrs, int defStyle ){ + super( context, attrs, defStyle ); + } + + /** + * Sets URL of the image that should be loaded into this view. Note that calling this will + * immediately either set the cached image (if available) or the default image specified by + * {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} on the view. + *

+ * NOTE: If applicable, {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} and + * {@link com.android.volley.toolbox.NetworkImageView#setErrorImageResId(int)} should be called prior to calling + * this function. + * + * @param url The URL that should be loaded into this ImageView. + * @param imageLoader ImageLoader that will be used to make the request. + */ + public void setImageUrl( String url, ImageLoader imageLoader ){ + mUrl = url; + mImageLoader = imageLoader; + // The URL has potentially changed. See if we need to load it. + loadImageIfNecessary( false ); + } + + /** + * Sets the default image resource ID to be used for this view until the attempt to load it + * completes. + */ + public void setDefaultImageResId( int defaultImage ){ + mDefaultImageId = defaultImage; + } + + /** + * Sets the error image resource ID to be used for this view in the event that the image + * requested fails to load. + */ + public void setErrorImageResId( int errorImage ){ + mErrorImageId = errorImage; + } + + float mCornerRadius; + + public void setCornerRadius( float r ){ + mCornerRadius = r; + } + + /** + * Loads the image for the view if it isn't already loaded. + * + * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. + */ + void loadImageIfNecessary( final boolean isInLayoutPass ){ + int width = getWidth(); + int height = getHeight(); + ScaleType scaleType = getScaleType(); + + boolean wrapWidth = false, wrapHeight = false; + if( getLayoutParams() != null ){ + wrapWidth = getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT; + wrapHeight = getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT; + } + + // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content + // view, hold off on loading the image. + boolean isFullyWrapContent = wrapWidth && wrapHeight; + if( width == 0 && height == 0 && ! isFullyWrapContent ){ + return; + } + + // if the URL to be loaded in this view is empty, cancel any old requests and clear the + // currently loaded image. + if( TextUtils.isEmpty( mUrl ) ){ + if( mImageContainer != null ){ + mImageContainer.cancelRequest(); + mImageContainer = null; + } + setDefaultImageOrNull(); + return; + } + + // if there was an old request in this view, check if it needs to be canceled. + if( mImageContainer != null && mImageContainer.getRequestUrl() != null ){ + if( mImageContainer.getRequestUrl().equals( mUrl ) ){ + // if the request is from the same URL, return. + return; + }else{ + // if there is a pre-existing request, cancel it if it's fetching a different URL. + mImageContainer.cancelRequest(); + setDefaultImageOrNull(); + } + } + + // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens. + int maxWidth = wrapWidth ? 0 : width; + int maxHeight = wrapHeight ? 0 : height; + + // The pre-existing content of this view didn't match the current URL. Load the new image + // from the network. + ImageLoader.ImageContainer newContainer = mImageLoader.get( mUrl, + new ImageLoader.ImageListener() { + @Override + public void onErrorResponse( VolleyError error ){ + if( mErrorImageId != 0 ){ + setImageResource( mErrorImageId ); + } + } + + @Override + public void onResponse( final ImageLoader.ImageContainer response, boolean isImmediate ){ + // If this was an immediate response that was delivered inside of a layout + // pass do not set the image immediately as it will trigger a requestLayout + // inside of a layout. Instead, defer setting the image by posting back to + // the main thread. + if( isImmediate && isInLayoutPass ){ + post( new Runnable() { + @Override + public void run(){ + onResponse( response, false ); + } + } ); + return; + } + + if( response.getBitmap() != null ){ + if( mCornerRadius > 0f ){ + RoundedBitmapDrawable d = RoundedBitmapDrawableFactory + .create( getResources(), response.getBitmap() ); + d.setCornerRadius( mCornerRadius ); + setImageDrawable( d ); + }else{ + setImageBitmap( response.getBitmap() ); + + } + }else if( mDefaultImageId != 0 ){ + setImageResource( mDefaultImageId ); + } + } + }, maxWidth, maxHeight, scaleType ); + + // update the ImageContainer to be the new bitmap container. + mImageContainer = newContainer; + } + + private void setDefaultImageOrNull(){ + if( mDefaultImageId != 0 ){ + setImageResource( mDefaultImageId ); + }else{ + setImageBitmap( null ); + } + } + + @Override + protected void onLayout( boolean changed, int left, int top, int right, int bottom ){ + super.onLayout( changed, left, top, right, bottom ); + loadImageIfNecessary( true ); + } + + @Override + protected void onDetachedFromWindow(){ + if( mImageContainer != null ){ + // If the view was bound to an image request, cancel it and clear + // out the image from the view. + mImageContainer.cancelRequest(); + setImageBitmap( null ); + // also clear out the container so we can reload the image if necessary. + mImageContainer = null; + } + super.onDetachedFromWindow(); + } + + @Override + protected void drawableStateChanged(){ + super.drawableStateChanged(); + invalidate(); + } +} diff --git a/app/src/main/res/layout/act_post.xml b/app/src/main/res/layout/act_post.xml index 558a485f..3a110c9f 100644 --- a/app/src/main/res/layout/act_post.xml +++ b/app/src/main/res/layout/act_post.xml @@ -54,7 +54,7 @@ android:orientation="horizontal" > - - - - - - - - - - - - - - - - - - -