I am building an Android application, using the Xamarin framework, that displays profile photos for users. The photos may either be drawn within an oval, like you'd see on any social network, or in a rectangle based on what screen the user is on. The images I am drawing are downloaded from a web service and may be any size. Thus I must scale the photos, fix any rotation issues, and then draw them either in a rectangle or an oval.
The entire process I have works fine, but the problem I have is when I load several of the profile views into a grid view, then the performance of the scroll event becomes very choppy and slow. I also get warnings in the console saying that 30 frames have been skipped, and that I may be doing to much on the main thread.
To try and fix the issue I've pushed the loading of the images to a background task, and that is also working great. However, the actual onDraw event is drawn on the main thread as it's drawn by the OS.
I've tracked down the issue to the canvas.drawBitmap() function call. When I comment out this command then the slow scrolling issues goes away, and I no longer get the warnings in the logs. This tells me that my process is efficient enough without the draw command, but when I add the draw command I start to get performance issues.
Does anyone know how I can optimize the drawing process so that I can achieve smooth scrolling? My code is below.
This is my grid view adapter class that loads the profile photo views...
namespace AndroidApp
{
public class GridViewAdapter : ArrayAdapter<UserContact>
{
private Activity ParentActivity;
#region Interface
public interface GridViewAdapterCallback
{
void DidSelectRow(Conversation conversation);
}
#endregion
#region Initialization
private int LayoutResourceId;
private List<UserContact> Data = null;
public GridViewAdapterCallback callback;
public GridViewAdapter (Context context, int layoutResourceId, List<UserContact> data, Activity parentActivity) : base (context, layoutResourceId, data)
{
this.LayoutResourceId = layoutResourceId;
this.Data = data;
this.ParentActivity = parentActivity;
}
public void SetData (List<UserContact> data)
{
this.Data = data;
}
#endregion
#region List Delegates
public override Android.Views.View GetView (int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater inflater = LayoutInflater.From (this.Context);
convertView = inflater.Inflate(this.LayoutResourceId, parent, false);
}
UserContact row = this.Data[position];
this.SetContactToGridItem (convertView, row);
return convertView;
}
#endregion
#region Setters
private void SetContactToGridItem (Android.Views.View view, UserContact contact)
{
// get view references
ProfileImageView imageView = view.FindViewById(Resource.Id.profileImageView).JavaCast<ProfileImageView>();
imageView.ProfileImageViewStyle = ProfileImageViewStyle.Square;
imageView.ResetImage ();
imageView.SetContact (contact, this.ParentActivity);
TextView textView = (TextView)view.FindViewById(Resource.Id.textView);
textView.SetText (contact.GetFullName (), TextView.BufferType.Normal);
}
#endregion
}
}
This is my main profile view class...
namespace AndroidApp
{
public enum ProfileImageViewColor
{
Default = 0,
White = 1
};
public enum ProfileImageViewStyle
{
Default = 0,
Square = 1
};
public class ProfileImageView : ImageView
{
public ProfileImageViewColor ProfileImageViewColor { get; set; }
public ProfileImageViewStyle ProfileImageViewStyle { get; set; }
private User User { get; set; }
private UserContact Contact { get; set; }
Bitmap Bitmap;
private Activity Activity { get; set; }
public ProfileImageView (System.IntPtr intPtr, Android.Runtime.JniHandleOwnership owner) : base (intPtr, owner)
{
Initialize ();
}
public ProfileImageView (Context context) : base (context)
{
Initialize ();
}
public ProfileImageView (Context context, IAttributeSet attrs) : base (context, attrs)
{
Initialize ();
}
public ProfileImageView (Context context, IAttributeSet attrs, int defStyle) : base (context, attrs, defStyle)
{
Initialize ();
}
void Initialize ()
{
this.ProfileImageViewColor = ProfileImageViewColor.Default;
this.ProfileImageViewStyle = ProfileImageViewStyle.Default;
this.SetScaleType (ScaleType.FitCenter);
this.CropToPadding = true;
}
#region Setters
public void SetUser (User user, Activity activity)
{
this.User = user;
this.Activity = activity;
if (this.User != null)
{
byte[] imageData = this.User.GetProfilePhoto ();
if (imageData != null)
{
this.SetImageData (this.User.GetProfilePhotoPath (), imageData);
}
else
{
UserBusiness.GetUserPhoto (this.User, (Shared.Error error, User usr) => {
activity.RunOnUiThread (() => {
this.SetImageData (this.User.GetProfilePhotoPath (), this.User.GetProfilePhoto ());
});
});
}
}
}
public void SetContact (UserContact contact, Activity activity)
{
this.Contact = contact;
this.Activity = activity;
if (this.Contact != null)
{
byte[] imageData = this.Contact.GetProfilePhoto();
if (imageData != null)
{
this.SetImageData (this.Contact.GetProfilePhotoPath (), imageData);
}
else
{
UserContactPhotoDownloadManager.CreateManager ().DownloadProfilePhoto (contact, (Shared.Error error, UserContact userContact) => {
activity.RunOnUiThread (() => {
this.SetImageData (this.Contact.GetProfilePhotoPath (), this.Contact.GetProfilePhoto ());
});
});
}
}
}
public override void SetImageBitmap (Bitmap bitmap)
{
if (bitmap == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
ProfileImageDrawable drawable = new ProfileImageDrawable (bitmap, this.Width, this.Height, this.ProfileImageViewStyle);
this.SetImageDrawable (drawable);
}
}
public void ResetImage ()
{
if (this.Bitmap != null)
this.Bitmap.Recycle ();
this.SetImageBitmap (null);
}
#endregion
#region Private
private void SetImageData (byte[] imageData)
{
if (this.Bitmap != null)
{
this.Bitmap.Recycle ();
this.Bitmap = null;
}
if (imageData == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
this.Bitmap = BitmapFactory.DecodeByteArray (imageData, 0, imageData.Length);
ProfileImageDrawable drawable = new ProfileImageDrawable (this.Bitmap, this.Width, this.Height, this.ProfileImageViewStyle);
this.SetImageDrawable (drawable);
}
}
private void SetImageData (string filePath, byte[] imageData)
{
if (this.Bitmap != null)
{
this.Bitmap.Recycle ();
this.Bitmap = null;
}
if (imageData == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
this.SetImageAsync (filePath);
}
}
private void SetImageAsync (string filePath)
{
ImageDrawTask task = new ImageDrawTask (filePath, this, (Drawable drawable) => {
this.Activity.RunOnUiThread (() => {
this.SetImageDrawable (drawable);
});
});
task.Execute ();
}
private int GetDefaultProfileImageID ()
{
if (this.ProfileImageViewColor == ProfileImageViewColor.Default)
return (int)typeof (Resource.Drawable).GetField ("profile_image_placeholder").GetValue (null);
else
return (int)typeof (Resource.Drawable).GetField ("profile_image_placeholder_white").GetValue (null);
}
#endregion
}
public class ImageDrawTask: AsyncTask {
public delegate void ImageDrawTaskCompletion (Drawable drawable);
private string FilePath;
private ProfileImageView ImageView;
private ImageDrawTaskCompletion Completion;
public ImageDrawTask (string filePath, ProfileImageView imageView, ImageDrawTaskCompletion completion)
{
this.FilePath = filePath;
this.ImageView = imageView;
this.Completion = completion;
}
protected override void OnPreExecute()
{
}
protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] #params)
{
Bitmap bitmap = ImageUtilities.FixRotation (this.FilePath);
ProfileImageDrawable drawable = new ProfileImageDrawable (bitmap, this.ImageView.Width, this.ImageView.Height, this.ImageView.ProfileImageViewStyle);
Completion (drawable);
return null;
}
protected override void OnPostExecute(Java.Lang.Object result)
{
}
}
}
And finally here is my drawable class...
namespace AndroidApp
{
public class ProfileImageDrawable : Drawable
{
Bitmap Bitmap;
ProfileImageViewStyle Style;
int Width;
int Height;
private RectF DrawFrame;
public ProfileImageDrawable (Bitmap bmp, int width, int height, ProfileImageViewStyle style)
{
this.Bitmap = bmp;
this.Style = style;
this.DrawFrame = new RectF ();
this.Width = width;
this.Height = height;
}
public override void Draw (Canvas canvas)
{
if (this.Style == ProfileImageViewStyle.Square)
{
canvas.DrawBitmap (this.Bitmap, this.GetMatrix (this.Bitmap, this.Width, this.Height), null);
}
else
{
BitmapShader bmpShader = new BitmapShader (this.Bitmap, Shader.TileMode.Clamp, Shader.TileMode.Clamp);
bmpShader.SetLocalMatrix (this.GetMatrix (this.Bitmap, this.Width, this.Height));
Paint paint = new Paint () { AntiAlias = true, Dither = true };
paint.SetShader (bmpShader);
canvas.DrawOval (this.DrawFrame, paint);
}
}
protected override void OnBoundsChange (Rect bounds)
{
base.OnBoundsChange (bounds);
this.DrawFrame.Set (0, 0, bounds.Width (), bounds.Height ());
}
public override int IntrinsicWidth {
get {
return this.Width;
}
}
public override int IntrinsicHeight {
get {
return this.Height;
}
}
public override void SetAlpha (int alpha)
{
}
public override int Opacity {
get {
return (int)Format.Opaque;
}
}
public override void SetColorFilter (ColorFilter cf)
{
}
private Matrix GetMatrix (Bitmap bmp, int width, int height)
{
Matrix mtx = new Matrix ();
float scaleWidth = ((float) width) / bmp.Width;
float scaleHeight = ((float) height) / bmp.Height;
float newWidth = 0;
float newHeight = 0;
if (scaleWidth > scaleHeight)
{
mtx.PostScale (scaleWidth, scaleWidth);
newWidth = scaleWidth * bmp.Width;
newHeight = scaleWidth * bmp.Height;
}
else
{
mtx.PostScale (scaleHeight, scaleHeight);
newWidth = scaleHeight * bmp.Width;
newHeight = scaleHeight * bmp.Height;
}
mtx.PostTranslate ((width - newWidth) / 2, (height - newHeight) / 2);
return mtx;
}
}
}
Related
I have custom view (extends LinearLayout) which contains RecyclerView. When I add new items RecyclerView doesn't change size. I think problem is that my custom view doesn`t give enough space to RecyclerView.
The question: How to change heigh of custom view depends on chlid recylerview size?
If I'm wrong, then correct me please
CustomView:
public class ExpandableView extends LinearLayout {
private Settings mSettings ;
private int mExpandState;
private ValueAnimator mExpandAnimator;
private ValueAnimator mParentAnimator;
private AnimatorSet mExpandScrollAnimatorSet;
private int mExpandedViewHeight;
private boolean mIsInit = true;
private boolean isAllowedExpand = false;
private ScrolledParent mScrolledParent;
private OnExpandListener mOnExpandListener;
public ExpandableView(Context context) {
super(context);
init(null);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(AttributeSet attrs) {
setOrientation(VERTICAL);
this.setClipChildren(false);
this.setClipToPadding(false);
mExpandState = ExpandState.PRE_INIT;
mSettings = new Settings();
if(attrs!=null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableView);
mSettings.expandDuration = typedArray.getInt(R.styleable.ExpandableView_expDuration, Settings.EXPAND_DURATION);
mSettings.expandWithParentScroll = typedArray.getBoolean(R.styleable.ExpandableView_expWithParentScroll,false);
mSettings.expandScrollTogether = typedArray.getBoolean(R.styleable.ExpandableView_expExpandScrollTogether,true);
typedArray.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
if(childCount!=2) {
throw new IllegalStateException("ExpandableLayout must has two child view !");
}
if(mIsInit) {
((MarginLayoutParams)getChildAt(0).getLayoutParams()).bottomMargin=0;
MarginLayoutParams marginLayoutParams = ((MarginLayoutParams)getChildAt(1).getLayoutParams());
marginLayoutParams.bottomMargin=0;
marginLayoutParams.topMargin=0;
marginLayoutParams.height = 0;
mExpandedViewHeight = getChildAt(1).getMeasuredHeight();
mIsInit =false;
mExpandState = ExpandState.CLOSED;
View view = getChildAt(0);
if (view != null){
view.setOnClickListener(v -> toggle());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mSettings.expandWithParentScroll) {
mScrolledParent = Utils.getScrolledParent(this);
}
}
private int getParentScrollDistance () {
int distance = 0;
if(mScrolledParent == null) {
return distance;
}
distance = (int) (getY() + getMeasuredHeight() + mExpandedViewHeight - mScrolledParent.scrolledView.getMeasuredHeight());
for(int index = 0; index < mScrolledParent.childBetweenParentCount; index++) {
ViewGroup parent = (ViewGroup) getParent();
distance+=parent.getY();
}
return distance;
}
private void verticalAnimate(final int startHeight, final int endHeight ) {
int distance = getParentScrollDistance();
final View target = getChildAt(1);
mExpandAnimator = ValueAnimator.ofInt(startHeight,endHeight);
mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
target.getLayoutParams().height = (int) animation.getAnimatedValue();
target.requestLayout();
}
});
mExpandAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if(endHeight-startHeight < 0) {
mExpandState = ExpandState.CLOSED;
if (mOnExpandListener != null) {
mOnExpandListener.onExpand(false);
}
} else {
mExpandState=ExpandState.EXPANDED;
if(mOnExpandListener != null) {
mOnExpandListener.onExpand(true);
}
}
}
});
//todo ??????????????????????
mExpandState=mExpandState==ExpandState.EXPANDED?ExpandState.CLOSING :ExpandState.EXPANDING;
mExpandAnimator.setDuration(mSettings.expandDuration);
if(mExpandState == ExpandState.EXPANDING && mSettings.expandWithParentScroll && distance > 0) {
mParentAnimator = Utils.createParentAnimator(mScrolledParent.scrolledView, distance, mSettings.expandDuration);
mExpandScrollAnimatorSet = new AnimatorSet();
if(mSettings.expandScrollTogether) {
mExpandScrollAnimatorSet.playTogether(mExpandAnimator,mParentAnimator);
} else {
mExpandScrollAnimatorSet.playSequentially(mExpandAnimator,mParentAnimator);
}
mExpandScrollAnimatorSet.start();
} else {
mExpandAnimator.start();
}
}
public void setExpand(boolean expand) {
if (mExpandState == ExpandState.PRE_INIT) {return;}
getChildAt(1).getLayoutParams().height=expand?mExpandedViewHeight:0;
requestLayout();
mExpandState=expand?ExpandState.EXPANDED:ExpandState.CLOSED;
}
public boolean isExpanded() {
return mExpandState==ExpandState.EXPANDED;
}
public void toggle() {
if (isAllowedExpand){
if(mExpandState==ExpandState.EXPANDED) {
close();
}else if(mExpandState==ExpandState.CLOSED) {
expand();
}
}
}
public void expand() {
verticalAnimate(0,mExpandedViewHeight);
}
public void close() {
verticalAnimate(mExpandedViewHeight,0);
}
public interface OnExpandListener {
void onExpand(boolean expanded) ;
}
public void setOnExpandListener(OnExpandListener onExpandListener) {
this.mOnExpandListener = onExpandListener;
}
public void setExpandScrollTogether(boolean expandScrollTogether) {
this.mSettings.expandScrollTogether = expandScrollTogether;
}
public void setExpandWithParentScroll(boolean expandWithParentScroll) {
this.mSettings.expandWithParentScroll = expandWithParentScroll;
}
public void setExpandDuration(int expandDuration) {
this.mSettings.expandDuration = expandDuration;
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mExpandAnimator!=null&&mExpandAnimator.isRunning()) {
mExpandAnimator.cancel();
mExpandAnimator.removeAllUpdateListeners();
}
if(mParentAnimator!=null&&mParentAnimator.isRunning()) {
mParentAnimator.cancel();
mParentAnimator.removeAllUpdateListeners();
}
if(mExpandScrollAnimatorSet!=null) {
mExpandScrollAnimatorSet.cancel();
}
}
public void setAllowedExpand(boolean allowedExpand) {
isAllowedExpand = allowedExpand;
}
public void refreshView(){
ViewGroup.LayoutParams params = this.getLayoutParams();
Log.d("tag", "params - " + params.height);
}
}
Specify your item height inside RecyclerView Adapter like below :
LinearLayout.LayoutParams relParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
relParams.height = 100;
relParams.width = Utility.getScreenWidth(mContext);
holder.yourDesireView.setLayoutParams(relParams);
Here is the screen width calculator method :
public static int getScreenWidth(Context context) {
if (context == null) {
return 0;
}
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
I think it will be helpful...
How can i set the default MenuItem for the official BottomNavigationView (com.android.support:design:25.0.1)?
If I call programmatically menuItem.setCheckable(true).setChecked(true) the zoom effect is not performed and the BottomNavigationView shows like this:
There is more simpler way to do this since Android Support Library 25.3.0 :
bottomNavigationView.setSelectedItemId(R.id.id_of_item_you_want_to_select_as_default);
I achieved this in a much simpler way:
//R.id.bottom_bar_icon is the icon you would like clicked by default
bottomNavigationView.getMenu().performIdentifierAction(R.id.bottom_bar_icon, 0);
//set the corresponding menu item to checked = true
//and the other items to checked = false
bottomNavigationView.getMenu().getItem(0).setChecked(false);
bottomNavigationView.getMenu().getItem(1).setChecked(true);
bottomNavigationView.getMenu().getItem(2).setChecked(false);
In the end I was able to achieve this issue extending the BottomNavigationView like this:
public class RichBottomNavigationView extends BottomNavigationView {
private ViewGroup mBottomItemsHolder;
private int mLastSelection = INVALID_POSITION;
private Drawable mShadowDrawable;
private boolean mShadowVisible = true;
private int mWidth;
private int mHeight;
private int mShadowElevation = 2;
public RichBottomNavigationView(Context context) {
super(context);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
;
}
private void init() {
mShadowDrawable = ContextCompat.getDrawable(getContext(), R.drawable.shadow);
if (mShadowDrawable != null) {
mShadowDrawable.setCallback(this);
}
setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
setShadowVisible(true);
setWillNotDraw(false);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h + mShadowElevation, oldw, oldh);
mWidth = w;
mHeight = h;
updateShadowBounds();
}
private void updateShadowBounds() {
if (mShadowDrawable != null && mBottomItemsHolder != null) {
mShadowDrawable.setBounds(0, 0, mWidth, mShadowElevation);
}
ViewCompat.postInvalidateOnAnimation(this);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mShadowDrawable != null && mShadowVisible) {
mShadowDrawable.draw(canvas);
}
}
public void setShadowVisible(boolean shadowVisible) {
setWillNotDraw(!mShadowVisible);
updateShadowBounds();
}
public int getShadowElevation() {
return mShadowVisible ? mShadowElevation : 0;
}
public int getSelectedItem() {
return mLastSelection = findSelectedItem();
}
#CallSuper
public void setSelectedItem(int position) {
if (position >= getMenu().size() || position < 0) return;
View menuItemView = getMenuItemView(position);
if (menuItemView == null) return;
MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();
itemData.setChecked(true);
boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
menuItemView.setSoundEffectsEnabled(false);
menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
menuItemView.performClick();
menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
menuItemView.setSoundEffectsEnabled(true);
mLastSelection = position;
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
BottomNavigationState state = new BottomNavigationState(superState);
mLastSelection = getSelectedItem();
state.lastSelection = mLastSelection;
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof BottomNavigationState)) {
super.onRestoreInstanceState(state);
return;
}
BottomNavigationState bottomNavigationState = (BottomNavigationState) state;
mLastSelection = bottomNavigationState.lastSelection;
dispatchRestoredState();
super.onRestoreInstanceState(bottomNavigationState.getSuperState());
}
private void dispatchRestoredState() {
if (mLastSelection != 0) { //Since the first item is always selected by the default implementation, dont waste time
setSelectedItem(mLastSelection);
}
}
private View getMenuItemView(int position) {
View bottomItem = mBottomItemsHolder.getChildAt(position);
if (bottomItem instanceof MenuView.ItemView) {
return bottomItem;
}
return null;
}
private int findSelectedItem() {
int itemCount = getMenu().size();
for (int i = 0; i < itemCount; i++) {
View bottomItem = mBottomItemsHolder.getChildAt(i);
if (bottomItem instanceof MenuView.ItemView) {
MenuItemImpl itemData = ((MenuView.ItemView) bottomItem).getItemData();
if (itemData.isChecked()) return i;
}
}
return INVALID_POSITION;
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mBottomItemsHolder = (ViewGroup) getChildAt(0);
updateShadowBounds();
//This sucks.
MarginLayoutParams layoutParams = (MarginLayoutParams) mBottomItemsHolder.getLayoutParams();
layoutParams.topMargin = (mShadowElevation + 2) / 2;
}
static class BottomNavigationState extends BaseSavedState {
public int lastSelection;
#RequiresApi(api = Build.VERSION_CODES.N)
public BottomNavigationState(Parcel in, ClassLoader loader) {
super(in, loader);
lastSelection = in.readInt();
}
public BottomNavigationState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(#NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(lastSelection);
}
public static final Parcelable.Creator<NavigationView.SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<NavigationView.SavedState>() {
#Override
public NavigationView.SavedState createFromParcel(Parcel parcel, ClassLoader loader) {
return new NavigationView.SavedState(parcel, loader);
}
#Override
public NavigationView.SavedState[] newArray(int size) {
return new NavigationView.SavedState[size];
}
});
}
}
and calling setSelectedItem(2)
I'm new to the game development in Android. This is what I'm trying to do
Render a background (green field) with items (items are behind the background but I've made them appear at the top for now)
Drawing an additional image (dig hole) on touch
Doing a collision test to see if user has found the item
I'm stuck with the second step as I'm not able to get it working at all. Blank screen appears as soon as touch event method invoked. Only a dig hole image is drawn on the blank screen. I've read thru the documents but couldn't figure out what's going wrong here.
Here is my code
class DrawSurface extends android.view.SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
Bitmap mBMPField;
Bitmap mBMPHole;
SurfaceHolder surfaceHolder;
float x = 0;
float y = 0;
boolean touched = false;
public DrawSurface(Context context) {
super(context);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public DrawSurface(Context context, AttributeSet attrSet) {
super(context, attrSet);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public DrawSurface(Context context, AttributeSet attrSet, int styleAttr) {
super(context, attrSet, styleAttr);
getHolder().addCallback(this);
setOnTouchListener(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("a", "Inside surfaceCreated");
setWillNotDraw(false);
mBMPField = BitmapFactory.decodeResource(getResources(),R.drawable.field);
mBMPHole = BitmapFactory.decodeResource(getResources(),R.drawable.hole);
surfaceHolder = holder;
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas();
Log.d(this.getClass().getName(), String.valueOf(canvas.hashCode()));
canvas.drawBitmap(mBMPField, 0, 0, null);
Paint paintText = new Paint();
paintText.setColor(Color.WHITE);
paintText.setStyle(Paint.Style.FILL);
paintText.setAntiAlias(true);
paintText.setTextSize(20);
ArrayList<Item> mItems = loadItems();
for (Item item : mItems) {
item.setX((int) (Math.random() * this.getWidth()));
item.setY((int) (Math.random() * this.getHeight()));
canvas.drawText(item.getText(), item.getX(), item.getY(), paintText);
}
}
finally
{
if(canvas!=null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("a", "Inside onTouch");
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
touched = true;
x = event.getX();
y = event.getY();
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas();
Log.d(this.getClass().getName(), String.valueOf(canvas.hashCode()));
canvas.drawBitmap(mBMPHole, x - mBMPHole.getWidth() / 2, y - mBMPHole.getHeight() / 2, null);
}
finally {
if(canvas!=null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
return false;
}
private class Item {
private int x;
private int y;
private String text;
private boolean found;
public boolean isFound() {
return found;
}
public void setFound(boolean found) {
this.found = found;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
private ArrayList<Item> loadItems()
{
InputStream input = getResources().openRawResource(R.raw.items);
BufferedReader reader = null;
ArrayList<Item> items = new ArrayList<Item>();
String line;
try
{
reader = new BufferedReader(new InputStreamReader(input));
while((line = reader.readLine()) != null)
{
Item item = new Item();
item.setText(line);
items.add(item);
}
}
catch (Exception e)
{
Log.e("MainActivity", "Reading list of Items failed!", e);
}
finally {
try
{
if (reader != null) reader.close();
}
catch (Exception e)
{
Log.e("MainActivity", "Error closing file reader.", e);
}
}
return items;
}
}
Any advice would greatly helpful here
I use Universal Image Loader to load images in a Jsoup parsed html. The <img> tags doesn't have a static position, they can appear anywhere in the Html element. And since I want them to appear in the positions where the <img> are, I can't give them an image view.
This is the class that I'm using to load the images
public class UILImageGetter implements Html.ImageGetter, View.OnClickListener{
Context c;
TextView conatiner;
UrlImageDownloader urlDrawable;
public UILImageGetter(View textView, Context context) {
this.c = context;
this.conatiner = (TextView) textView;
}
#Override
public Drawable getDrawable(String source) {
urlDrawable = new UrlImageDownloader(c.getResources(), source);
if (Build.VERSION.SDK_INT >= 21) {
urlDrawable.mDrawable = c.getResources().getDrawable(R.drawable.default_thumb,null);
} else {
urlDrawable.mDrawable = c.getResources().getDrawable(R.drawable.default_thumb);
}
ImageLoader.getInstance().loadImage(source, new SimpleListener(urlDrawable));
return urlDrawable;
}
#Override
public void onClick(View v) {
}
private class SimpleListener extends SimpleImageLoadingListener {
UrlImageDownloader mUrlImageDownloader;
public SimpleListener(UrlImageDownloader downloader) {
super();
mUrlImageDownloader= downloader;
}
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
int width = loadedImage.getWidth();
int height = loadedImage.getHeight();
int newWidth = width;
int newHeight = height;
if (width > conatiner.getWidth()) {
newWidth = conatiner.getWidth();
newHeight = (newWidth * height) / width;
}
if (view != null) {
view.getLayoutParams().width = newWidth;
view.getLayoutParams().height = newHeight;
}
Drawable result = new BitmapDrawable(c.getResources(), loadedImage);
result.setBounds(0, 0, newWidth, newHeight);
mUrlImageDownloader.setBounds(0, 0, newWidth, newHeight);
mUrlImageDownloader.mDrawable = result;
conatiner.setHeight((conatiner.getHeight() + result.getIntrinsicHeight()));
//conatiner.invalidate();
}
}
private class UrlImageDownloader extends BitmapDrawable {
public Drawable mDrawable;
public UrlImageDownloader(Resources resources, String filepath) {
super(resources, filepath);
mDrawable = new BitmapDrawable(resources, filepath);
}
#Override
public void draw(Canvas canvas) {
if (mDrawable != null) {
mDrawable.draw(canvas);
}
}
}
}
My problem is how to set OnclickListener on the images and get them to display (in a dialog) the original height and width when clicked.
I'm using Dave Morrissey's Subsampling Scale Image View. I'm using his Pinview example (as shown here: https://github.com/davemorrissey/subsampling-scale-image-view/blob/master/sample/src/com/davemorrissey/labs/subscaleview/sample/extension/views/PinView.java)
What I've done differently is that I've created an ArrayList of Bitmaps that are Pins. But I want to make each pin clickable to set off an on click function. I know Bitmaps can not be clicked on. I have multiple pins on a map image and would like for each pin to be associated with an object.
What would be the best approach to accomplish this?
Note: I did override the setOnClickListener method inside of the Pinview class, but what happens that all pins that were dropped become associated to the same object. And that clearing 1 pin would then clear all pins.
The model that stores the bitmap, pointF and point name:
public class CategoryPoint {
private String category;
private Bitmap image;
private PointF pointF;
public CategoryPoint(String category, Bitmap image, PointF pointF) {
this.category = category;
this.image = image;
this.pointF = pointF;
}
// getters/setters
}
View looks like this:
public class PinsView extends SubsamplingScaleImageView {
private OnPinClickListener onPinClickListener;
private final Paint paint = new Paint();
private List<CategoryPoint> categoryPoints;
public PinsView(Context context) {
this(context, null);
}
public PinsView(Context context, AttributeSet attr) {
super(context, attr);
categoryPoints = new ArrayList<>();
initTouchListener();
}
public void addCategories(List<CategoryPoint> categoryPoints) {
this.categoryPoints = categoryPoints;
invalidate();
}
public void removeCategories(List<CategoryPoint> categoryPoints) {
this.categoryPoints.removeAll(categoryPoints);
invalidate();
}
public void removeAllCategories() {
this.categoryPoints.clear();
invalidate();
}
public void setOnPinClickListener(OnPinClickListener listener) {
onPinClickListener = listener;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isReady()) {
return;
}
paint.setAntiAlias(true);
for (CategoryPoint categoryPoint: categoryPoints) {
Bitmap pinIcon = categoryPoint.getImage();
if (categoryPoint.getPointF() != null && categoryPoint.getImage() != null) {
PointF point = sourceToViewCoord(categoryPoint.getPointF());
float vX = point.x - (pinIcon.getWidth()/2);
float vY = point.y - pinIcon.getHeight();
canvas.drawBitmap(pinIcon, vX, vY, paint);
}
}
}
private void initTouchListener() {
GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (isReady() && categoryPoints != null) {
PointF tappedCoordinate = new PointF(e.getX(), e.getY());
Bitmap clickArea = categoryPoints.get(0).getImage();
int clickAreaWidth = clickArea.getWidth();
int clickAreaHeight = clickArea.getHeight();
for (CategoryPoint categoryPoint : categoryPoints) {
PointF categoryCoordinate = sourceToViewCoord(categoryPoint.getPointF());
int categoryX = (int) (categoryCoordinate.x);
int categoryY = (int) (categoryCoordinate.y - clickAreaHeight / 2);
if (tappedCoordinate.x >= categoryX - clickAreaWidth / 2
&& tappedCoordinate.x <= categoryX + clickAreaWidth / 2
&& tappedCoordinate.y >= categoryY - clickAreaHeight / 2
&& tappedCoordinate.y <= categoryY + clickAreaHeight / 2) {
onPinClickListener.onPinClick(categoryPoint);
break;
}
}
}
return true;
}
});
setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
}
}
Fragment:
pinView.setOnImageEventListener(this);
pinView.setOnPinClickListener(this);
// implementation