18

Background:

  • Google suggests to avoid using nested weighted linearLayouts because of performance.
  • using nested weighted linearLayout is awful to read, write and maintain.
  • there is still no good alternative for putting views that are % of the available size. Only solutions are weights and using OpenGL. There isn't even something like the "viewBox" shown on WPF/Silverlight to auto-scale things.

This is why I've decided to create my own layout which you tell for each of its children exactly what should be their weights (and surrounding weights) compared to its size.

It seems I've succeeded , but for some reason I think there are some bugs which I can't track down.

One of the bugs is that textView, even though I give a lot of space for it, it puts the text on the top instead of in the center. imageViews on the other hand work very well. Another bug is that if I use a layout (for example a frameLayout) inside my customized layout, views within it won't be shown (but the layout itself will).

Please help me figure out why it occurs.

How to use: instead of the next usage of linear layout (I use a long XML on purpose, to show how my solution can shorten things):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" android:orientation="vertical">

  <View android:layout_width="wrap_content" android:layout_height="0px"
    android:layout_weight="1" />

  <LinearLayout android:layout_width="match_parent"
    android:layout_height="0px" android:layout_weight="1"
    android:orientation="horizontal">

    <View android:layout_width="0px" android:layout_height="wrap_content"
      android:layout_weight="1" />

    <TextView android:layout_width="0px" android:layout_weight="1"
      android:layout_height="match_parent" android:text="@string/hello_world"
      android:background="#ffff0000" android:gravity="center"
      android:textSize="20dp" android:textColor="#ff000000" />

    <View android:layout_width="0px" android:layout_height="wrap_content"
      android:layout_weight="1" />

  </LinearLayout>
  <View android:layout_width="wrap_content" android:layout_height="0px"
    android:layout_weight="1" />
</LinearLayout>

What I do is simply (the x is where to put the view itself in the weights list):

<com.example.weightedlayouttest.WeightedLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res/com.example.weightedlayouttest"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">

  <TextView android:layout_width="0px" android:layout_height="0px"
    app:horizontalWeights="1,1x,1" app:verticalWeights="1,1x,1"
    android:text="@string/hello_world" android:background="#ffff0000"
    android:gravity="center" android:textSize="20dp" android:textColor="#ff000000" />

</com.example.weightedlayouttest.WeightedLayout>

My code of the special layout is:

public class WeightedLayout extends ViewGroup
  {
  @Override
  protected WeightedLayout.LayoutParams generateDefaultLayoutParams()
    {
    return new WeightedLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
    }

  @Override
  public WeightedLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
    {
    return new WeightedLayout.LayoutParams(getContext(),attrs);
    }

  @Override
  protected ViewGroup.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
    {
    return new WeightedLayout.LayoutParams(p.width,p.height);
    }

  @Override
  protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
    {
    final boolean isCorrectInstance=p instanceof WeightedLayout.LayoutParams;
    return isCorrectInstance;
    }

  public WeightedLayout(final Context context)
    {
    super(context);
    }

  public WeightedLayout(final Context context,final AttributeSet attrs)
    {
    super(context,attrs);
    }

  public WeightedLayout(final Context context,final AttributeSet attrs,final int defStyle)
    {
    super(context,attrs,defStyle);
    }

  @Override
  protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
    {
    for(int i=0;i<this.getChildCount();++i)
      {
      final View v=getChildAt(i);
      final WeightedLayout.LayoutParams layoutParams=(WeightedLayout.LayoutParams)v.getLayoutParams();
      //
      final int availableWidth=r-l;
      final int totalHorizontalWeights=layoutParams.getLeftHorizontalWeight()+layoutParams.getViewHorizontalWeight()+layoutParams.getRightHorizontalWeight();
      final int left=l+layoutParams.getLeftHorizontalWeight()*availableWidth/totalHorizontalWeights;
      final int right=r-layoutParams.getRightHorizontalWeight()*availableWidth/totalHorizontalWeights;
      //
      final int availableHeight=b-t;
      final int totalVerticalWeights=layoutParams.getTopVerticalWeight()+layoutParams.getViewVerticalWeight()+layoutParams.getBottomVerticalWeight();
      final int top=t+layoutParams.getTopVerticalWeight()*availableHeight/totalVerticalWeights;
      final int bottom=b-layoutParams.getBottomVerticalWeight()*availableHeight/totalVerticalWeights;
      //
      v.layout(left+getPaddingLeft(),top+getPaddingTop(),right+getPaddingRight(),bottom+getPaddingBottom());
      }
    }

  // ///////////////
  // LayoutParams //
  // ///////////////
  public static class LayoutParams extends ViewGroup.LayoutParams
    {
    int _leftHorizontalWeight =0,_rightHorizontalWeight=0,_viewHorizontalWeight=0;
    int _topVerticalWeight    =0,_bottomVerticalWeight=0,_viewVerticalWeight=0;

    public LayoutParams(final Context context,final AttributeSet attrs)
      {
      super(context,attrs);
      final TypedArray arr=context.obtainStyledAttributes(attrs,R.styleable.WeightedLayout_LayoutParams);
        {
        final String horizontalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_horizontalWeights);
        //
        // handle horizontal weight:
        //
        final String[] words=horizontalWeights.split(",");
        boolean foundViewHorizontalWeight=false;
        int weight;
        for(final String word : words)
          {
          final int viewWeightIndex=word.lastIndexOf('x');
          if(viewWeightIndex>=0)
            {
            if(foundViewHorizontalWeight)
              throw new IllegalArgumentException("found more than one weights for the current view");
            weight=Integer.parseInt(word.substring(0,viewWeightIndex));
            setViewHorizontalWeight(weight);
            foundViewHorizontalWeight=true;
            }
          else
            {
            weight=Integer.parseInt(word);
            if(weight<0)
              throw new IllegalArgumentException("found negative weight:"+weight);
            if(foundViewHorizontalWeight)
              _rightHorizontalWeight+=weight;
            else _leftHorizontalWeight+=weight;
            }
          }
        if(!foundViewHorizontalWeight)
          throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
        }
        //
        // handle vertical weight:
        //
        {
        final String verticalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_verticalWeights);
        final String[] words=verticalWeights.split(",");
        boolean foundViewVerticalWeight=false;
        int weight;
        for(final String word : words)
          {
          final int viewWeightIndex=word.lastIndexOf('x');
          if(viewWeightIndex>=0)
            {
            if(foundViewVerticalWeight)
              throw new IllegalArgumentException("found more than one weights for the current view");
            weight=Integer.parseInt(word.substring(0,viewWeightIndex));
            setViewVerticalWeight(weight);
            foundViewVerticalWeight=true;
            }
          else
            {
            weight=Integer.parseInt(word);
            if(weight<0)
              throw new IllegalArgumentException("found negative weight:"+weight);
            if(foundViewVerticalWeight)
              _bottomVerticalWeight+=weight;
            else _topVerticalWeight+=weight;
            }
          }
        if(!foundViewVerticalWeight)
          throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
        }
      //
      arr.recycle();
      }

    public LayoutParams(final int width,final int height)
      {
      super(width,height);
      }

    public LayoutParams(final ViewGroup.LayoutParams source)
      {
      super(source);
      }

    public int getLeftHorizontalWeight()
      {
      return _leftHorizontalWeight;
      }

    public void setLeftHorizontalWeight(final int leftHorizontalWeight)
      {
      _leftHorizontalWeight=leftHorizontalWeight;
      }

    public int getRightHorizontalWeight()
      {
      return _rightHorizontalWeight;
      }

    public void setRightHorizontalWeight(final int rightHorizontalWeight)
      {
      if(rightHorizontalWeight<0)
        throw new IllegalArgumentException("negative weight :"+rightHorizontalWeight);
      _rightHorizontalWeight=rightHorizontalWeight;
      }

    public int getViewHorizontalWeight()
      {
      return _viewHorizontalWeight;
      }

    public void setViewHorizontalWeight(final int viewHorizontalWeight)
      {
      if(viewHorizontalWeight<0)
        throw new IllegalArgumentException("negative weight:"+viewHorizontalWeight);
      _viewHorizontalWeight=viewHorizontalWeight;
      }

    public int getTopVerticalWeight()
      {
      return _topVerticalWeight;
      }

    public void setTopVerticalWeight(final int topVerticalWeight)
      {
      if(topVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+topVerticalWeight);
      _topVerticalWeight=topVerticalWeight;
      }

    public int getBottomVerticalWeight()
      {
      return _bottomVerticalWeight;
      }

    public void setBottomVerticalWeight(final int bottomVerticalWeight)
      {
      if(bottomVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+bottomVerticalWeight);
      _bottomVerticalWeight=bottomVerticalWeight;
      }

    public int getViewVerticalWeight()
      {
      return _viewVerticalWeight;
      }

    public void setViewVerticalWeight(final int viewVerticalWeight)
      {
      if(viewVerticalWeight<0)
        throw new IllegalArgumentException("negative weight :"+viewVerticalWeight);
      _viewVerticalWeight=viewVerticalWeight;
      }
    }
  }
Luuklag
  • 3,897
  • 11
  • 38
  • 57
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Do you have a specific scenario to solve with this layout or you're trying to build something generic? Keep in mind that the `LinearLayout` is a fairly complex layout. – user Nov 04 '12 at 11:13
  • no i want to make a nice generaic solution which will only replace the weights of the linearLayout , but it's not that i want to make something like linearLayout . it's more like a frameLayout that you can put any view anywhere and in any size , where the size&coordinates of the children are set based on the size of the parent . look at the example . – android developer Nov 04 '12 at 13:26
  • Wouldn't it have been simpler for you to just use `GridLayout`? – CommonsWare Nov 04 '12 at 22:10
  • i don't think a gridLayout can help here since gridLayout lacks many features that can be done here . for example , how would you use a gridLayout for when 2 views can be on top of one another , each has a different size and location compared to the parent? correct me if i'm wrong , but gridlayout requires you to have the views one besides (or beneath ) the other , and it also requires you to prepare the cells for each of them . all this , while my solution is very short and clear . – android developer Nov 04 '12 at 22:44
  • "...using nested weighted linearLayout is awful to read , write and maintain ..." I disagree. For really nasty layouts start using `` and `` [tags](http://developer.android.com/training/improving-layouts/reusing-layouts.html#Merge). – bgs Nov 05 '12 at 16:08
  • kindly read the example i've written . what i offer is far shorter . also , putting an include tag doesn't mean that what you've written is gone . it just moves to somewhere else . the linearLayout solution can sometimes require a new linearLayout per view (especially if you use layers of views) . what i offer is a single layout for all of the views and it should be much more efficient in both memory and speed. google's lint says that nested linearLayouts is a bad thing for performance ,so imagine many of them being used. – android developer Nov 05 '12 at 16:42
  • 1
    I think you're using a shotgun to kill a house fly. Any layout can be be accomplished without nesting more than 2 or 3 deep. The "bad for performance" warnings exist to deter people from using nested weights in situations where it can be prevented with better planning. Nested weights are ugly. They are ugly in HTML as well. However, if your layout requires a child to be a % of it's parent that is a % of it's parent... I'd like to see a situation that would greatly impact performance and couldn't be solved with the existing layout tools. – Jason Hessley Nov 06 '12 at 21:39
  • kindly try to make an app that has 2 layers of images , where one image is full screen and another is part of the screen , and put the second on top of it so that it matches the bottom layer like a puzzle . when using linearLayout weights , on some screens , the pieces don't show well and move aside . in any case , sometimes people want to have automatically scaled images in order to avoid so many configurations (of density/screen size/...) . – android developer Nov 07 '12 at 09:27
  • I have created my own way of setting views width and height in percentage. Refer this for more http://stackoverflow.com/a/16518557/1939564 – Muhammad Babar Dec 05 '13 at 11:36
  • @MuhammadBabar I think the best and most comfortable thing is to have it all on XML and not in code, since you can see how it would look like on the UI designer. – android developer Dec 05 '13 at 15:01
  • Yeah i know that what i discover is the way to set views width and height in term of percentage. I know that there are some rare case where you need this but this works really good – Muhammad Babar Dec 05 '13 at 16:10
  • I always use nested weighted.... without using "nested weighted" its very very difficult – Shirish Herwade Mar 03 '15 at 14:44
  • @android developer Any one looking for demo of percent support library http://code2concept.blogspot.in/2015/08/android-percent-support-lib-sample.html – Nitesh Tiwari Aug 31 '15 at 07:41
  • @nitesh I've shown it in the last post I've created , including code, here: http://stackoverflow.com/a/31792245/878126 – android developer Aug 31 '15 at 13:51

4 Answers4

8

I accepted your challenge and attempted to create the layout you describe in response to my comment. You are right. It is surprisingly difficult to accomplish. Besides that, I do like shooting house flies. So I jumped on board and came up with this solution.

  1. Extend the existing layout classes rather than creating your own from scratch. I went with RelativeLayout to start with but the same approach can be used by all of them. This gives you the ability to use the default behavior for that layout on child views that you don't want to manipulate.

  2. I added four attributes to the layout called top, left, width and height. My intention was to mimic HTML by allowing values such as "10%", "100px", "100dp" etc.. At this time the only value accepted is an integer representing the % of parent. "20" = 20% of the layout.

  3. For better performance I allow the super.onLayout() to execute through all of it's iterations and only manipulate the views with the custom attributes on it's last pass. Since these views will be positioned and scaled independently of the siblings we can move them after everything else has settled.

Here is atts.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="HtmlStyleLayout">
        <attr name="top" format="integer"/>
        <attr name="left" format="integer"/>
        <attr name="height" format="integer"/>
        <attr name="width" format="integer"/>

    </declare-styleable>
</resources>

Here is my layout class.

package com.example.helpso;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;


public class HtmlStyleLayout extends RelativeLayout{

    private int pass =0;
    @Override
      protected HtmlStyleLayout.LayoutParams generateDefaultLayoutParams()
        {
        return new HtmlStyleLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
        }

      @Override
      public HtmlStyleLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
        {
        return new HtmlStyleLayout.LayoutParams(getContext(),attrs);
        }

      @Override
      protected RelativeLayout.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
        {
        return new HtmlStyleLayout.LayoutParams(p.width,p.height);
        }

      @Override
      protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
        {
        final boolean isCorrectInstance=p instanceof HtmlStyleLayout.LayoutParams;
        return isCorrectInstance;
        }

    public HtmlStyleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public void setScaleType(View v){
        try{
            ((ImageView) v).setScaleType (ImageView.ScaleType.FIT_XY);
        }catch (Exception e){
            // The view is not an ImageView 
        }
    }


    @Override
      protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
        {
        super.onLayout(changed, l, t, r, b);           //Let the parent layout do it's thing


        pass++;                                        // After the last pass of
        final int childCount = this.getChildCount();   // the parent layout
        if(true){                        // we do our thing


            for(int i=0;i<childCount;++i)
              {
              final View v=getChildAt(i);
              final HtmlStyleLayout.LayoutParams params = (HtmlStyleLayout.LayoutParams)v.getLayoutParams();

              int newTop = v.getTop();                 // set the default value
              int newLeft = v.getLeft();               // of these to the value
              int newBottom = v.getBottom();           // set by super.onLayout() 
              int newRight= v.getRight();             
              boolean viewChanged = false;

              if(params.getTop() >= 0){
                  newTop = ( (int) ((b-t) * (params.getTop() * .01))  );
                  viewChanged = true;
              }

              if(params.getLeft() >= 0){
                  newLeft = ( (int) ((r-l) * (params.getLeft() * .01))  );
                  viewChanged = true;
              }

              if(params.getHeight() > 0){
                  newBottom = ( (int) ((int) newTop + ((b-t) * (params.getHeight() * .01)))  );
                  setScaleType(v);                        // set the scale type to fitxy
                  viewChanged = true;
              }else{
                  newBottom = (newTop + (v.getBottom() - v.getTop()));
                  Log.i("heightElse","v.getBottom()=" +
                          Integer.toString(v.getBottom())
                          + " v.getTop=" +
                          Integer.toString(v.getTop()));
              }

              if(params.getWidth() > 0){
                  newRight = ( (int) ((int) newLeft + ((r-l) * (params.getWidth() * .01)))  );
                  setScaleType(v);
                  viewChanged = true;
              }else{
                  newRight = (newLeft + (v.getRight() - v.getLeft()));
              }

                // only call layout() if we changed something
                if(viewChanged)
                    Log.i("SizeLocation",
                            Integer.toString(i) + ": "
                            + Integer.toString(newLeft) + ", "
                            + Integer.toString(newTop) + ", "
                            + Integer.toString(newRight) + ", "
                            + Integer.toString(newBottom));
                v.layout(newLeft, newTop, newRight, newBottom);
              }





            pass = 0;                                 // reset the parent pass counter
        }
        }


     public  class LayoutParams extends RelativeLayout.LayoutParams
        {

        private int top, left, width, height;
        public LayoutParams(final Context context, final AttributeSet atts) {
            super(context, atts);
            TypedArray a = context.obtainStyledAttributes(atts, R.styleable.HtmlStyleLayout);
            top =  a.getInt(R.styleable.HtmlStyleLayout_top , -1);
            left = a.getInt(R.styleable.HtmlStyleLayout_left, -1);
            width = a.getInt(R.styleable.HtmlStyleLayout_width, -1);
            height = a.getInt(R.styleable.HtmlStyleLayout_height, -1);
            a.recycle();


        }
        public LayoutParams(int w, int h) {
            super(w,h);
            Log.d("lp","2");
        }
        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
            Log.d("lp","3");
        }
        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
            Log.d("lp","4");
        }
        public int getTop(){
            return top;
        }
        public int getLeft(){
            return left;
        }
        public int getWidth(){
            return width;
        }
        public int getHeight(){
            return height;
        }
        }
}

Here is an example activity xml

<com.example.helpso.HtmlStyleLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:html="http://schemas.android.com/apk/res/com.example.helpso"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:scaleType="fitXY"
        android:src="@drawable/bg" />

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/overlay"
        html:height="10"
        html:left="13"
        html:top="18"
        html:width="23" />

</com.example.helpso.HtmlStyleLayout>

Here are the images I used for testing.

bg enter image description here

If you do not set a value for a particular attribute it's default will be used. So if you set width but not height the image will scale in width and wrap_content for height.

Zipped project folder.

apk

I found the source of the bug. The problem is that I was using the layout's child count as in indicator of how many calls to onLayout it will make. This doesn't seem to hold true in older versions of android. I noticed in 2.1 onLayout is only called once. So I changed

if(pass == childCount){

to

if(true){  

and it started working as expected.

I still thinks it's beneficial to adjust the layout only after the super is done. Just need to find a better way to know when that is.

EDIT

I didn't realize that your intention was to patch together images with pixel by pixel precision. I achieved the precision you are looking for by using double float precision variables instead of integers. However, you will not be able accomplish this while allowing your images to scale. When an images is scaled up pixels are added at some interval between the existing pixels. The color of the new pixels are some weighted average of the surrounding pixels. When you scale the images independently of each other they don't share any information. The result is that you will always have some artifact at the seam. Add to that the result of rounding since you can't have a partial pixel and you will always have a +/-1 pixel tolerance.

To verify this you can attempt the same task in your premium photo editing software. I use PhotoShop. Using the same images as in my apk, I placed them in seperate files. I scaled them both by 168% vertically and 127% horizontally. I then placed them in a file together and attempted to align them. The result was exactly the same as is seen in my apk.

To demonstrate the accuracy of the layout, I added a second activity to my apk. On this activity I did not scale the background image. Everything else is exactly the same. The result is seamless.

I also added a button to show/hide the overlay image and one to switch between the activities.

I updated both the apk and the zipped project folder on my google drive. You can get them by the links above.

Jason Hessley
  • 1,608
  • 10
  • 8
  • i think you have a bug that causes both the designer and my device not to show the images on the correct position (i know you used % instead of weights , but even when playing with it it causes the image to be put on the top left corner on many cases) . – android developer Nov 08 '12 at 22:27
  • I'll post APK and project folder when I get back to my computer in about 3 hours. I'm not claiming this to be a fully developed solution. I've been working on it for less than a day. If it helps you that's great. I don't plan on doing anything with it myself. – Jason Hessley Nov 08 '12 at 22:30
  • sorry for being rude . i really appreciate the work . it just didn't work as i've expected. – android developer Nov 08 '12 at 22:31
  • I attempted to run it on Android 2.1 and encountered the position error you described. It works as expected on my device Android 4.0.1 and on AVD Emulator 4.1. I'm posting the apk and complete project folder. This is a very interesting project. I may come back to it when I have time. – Jason Hessley Nov 09 '12 at 01:28
  • Identified the cause of the position error. See edit in answer. – Jason Hessley Nov 09 '12 at 02:19
  • using percentage doesn't work well as it requires you to use fractions on some cases . also , it seems that the visual editor doesn't show you layout correctly . as a test , instead of using the "overlay" bitmap as a simple filled orange rectangle , use the real part that was cropped . try to put the "overlay" image perfectly on the same area that was cropped so that the user won't be able to tell the difference between 2 images (background image with a missing part and the "overlay" image which is the missing part) and 1 background image . – android developer Nov 09 '12 at 10:57
  • Achieved the precision you are looking for. See edit in answer. – Jason Hessley Nov 10 '12 at 03:50
  • i still don't get where the black/white lines came from . i think the scaling of the imageView's content itself isn't done correctly . should double precision be enough even for this relatively high resolution? if i keep the aspect ratio of both the background image and the overlay image , will this be fixed ? does the same problem exist even on openGL ? btw, now i don't know if i should give the award to you who made a totally new project and explained a lot , or to jaredzhang who actually fixed my code and reached the same result. – android developer Nov 10 '12 at 09:06
  • btw, you project still doesn't show the overlay image on the correct position on the visual editor within eclipse . – android developer Nov 10 '12 at 09:17
  • You should be able to add the data from both images to a Canvas. Then you could scale the Canvas to fit the screen as you want. Since both images would be part of the same Canvas before it is scaled, the scaling would be smooth. This is no longer a "Layout" though. It's more of an image processor. The use case would be very different. – Jason Hessley Nov 10 '12 at 11:15
  • so i will need to make my own very customized view , which can only support images (for now) , just to make the images look well ? would overriding the bitmap scaling (and giving them to the imageViews ) help instead of doing such a thing ? – android developer Nov 10 '12 at 13:21
4

After trying your code, I just find the reason of the problems you mentioned, and it is because in your customed layout, you only layout the child properly, however you forgot to measure your child properly, which will directly affect the drawing hierarchy, so simply add the below code, and it works for me.

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec)-this.getPaddingRight()-this.getPaddingRight();
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    int heightSize = MeasureSpec.getSize(heightMeasureSpec)-this.getPaddingTop()-this.getPaddingBottom();
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    if(heightMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.UNSPECIFIED)
        throw new IllegalArgumentException("the layout must have a exact size");

    for (int i = 0; i < this.getChildCount(); ++i) {
        View child = this.getChildAt(i);
        LayoutParams lp = (LayoutParams)child.getLayoutParams();
        int width = lp._viewHorizontalWeight * widthSize/(lp._leftHorizontalWeight+lp._rightHorizontalWeight+lp._viewHorizontalWeight);
        int height =  lp._viewVerticalWeight * heightSize/(lp._topVerticalWeight+lp._bottomVerticalWeight+lp._viewVerticalWeight);
        child.measure(width | MeasureSpec.EXACTLY,  height | MeasureSpec.EXACTLY);
    }

    this.setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));

}
jaredzhang
  • 1,158
  • 8
  • 12
  • you've fixed my code , but i still have weird extra stitching space when i do the "puzzle test" i've offered . so , for example , if you take a 720x1280 image for the galaxy s3 and put it full screen , and take an partial image of this image (cropping it from the original) and trying to put them together in the new layout , it might show black lines near the piece you've attached . even if it doesn't , it will be shown on other screens. is it possible to fix it? – android developer Nov 08 '12 at 22:29
  • i just tried, and i didn't see any black lines? where do you see them? and what do you mean by "it will be shown on other screens"? – jaredzhang Nov 09 '12 at 02:21
  • I think I got what you mean, it is not your layout problem, because the default scale type of ImageView is FIT_CENTER, so you will see those black stitching, try to see set the scale type to FIT_XY, everything will be fine. – jaredzhang Nov 09 '12 at 02:26
  • no , the stitches can be shown even with the scaleType set to fit_xy , and with adjustViewBounds set to false. you can test it out on your device by changing orientation . – android developer Nov 09 '12 at 10:39
1

Now there is a nicer solution than the custom layout I've made:

PercentRelativeLayout

Tutorial can be found here and a repo can be found here.

Example code:

<android.support.percent.PercentRelativeLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
     <ImageView
         app:layout_widthPercent="50%"
         app:layout_heightPercent="50%"
         app:layout_marginTopPercent="25%"
         app:layout_marginLeftPercent="25%"/>
 </android.support.percent.PercentFrameLayout/>

or:

 <android.support.percent.PercentFrameLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
     <ImageView
         app:layout_widthPercent="50%"
         app:layout_heightPercent="50%"
         app:layout_marginTopPercent="25%"
         app:layout_marginLeftPercent="25%"/>
 </android.support.percent.PercentFrameLayout/>

I wonder though if it can handle the issues I've shown here.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    But... But...! But ! BUT why only now...? I have just discovered this support library... And I want to tell every android developer it exists... – kmas Sep 01 '15 at 14:28
-1

I propose to use following optimizations:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" android:gravity="center">


    <TextView android:layout_width="wrap_content"
      android:layout_height="wrap_content" android:text="@string/hello_world"
      android:background="#ffff0000" android:gravity="center"
      android:textSize="20dp" android:textColor="#ff000000" />

</FrameLayout>

or use http://developer.android.com/reference/android/widget/LinearLayout.html#attr_android:weightSum

or use TableLayout with layout_weight for rows and columns

or use GridLayout.

Taras Shevchuk
  • 331
  • 2
  • 12
  • this is not what i have asked for . i know all of those solutions and none offer relative size and position . the only similar solution is using the weights of the linearLayout , and as i've shown ,this is a very long solution , has bad readability and has bad performance . – android developer Nov 06 '12 at 08:34