Last active
October 5, 2017 11:36
-
-
Save SachinR90/176858df71aeb5c0c2b2e48be1ac97ac to your computer and use it in GitHub Desktop.
CustomFrameLayout - This layout specifies its own width/height based on the Device/Parent width/height.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="CustomFrameLayout"> | |
<!--Divides device/parent width by specified value and applies the result to its own width--> | |
<attr name="divideWidthBy" format="float"/> | |
<!--Divides device/parent height by specified value and applies the result to its own height--> | |
<attr name="divideHeightBy" format="float"/> | |
<!--When we square layout, we to make square based on with or base on height . Set false to have a square layout based on height priority--> | |
<attr name="isWidthPreferred" format="boolean"/> | |
<!--Division is performed based on the following types --> | |
<attr name="RatioType" format="enum"> | |
<!--Square - makes the layout to have equal width and height. isWidthPreferred if provided false makes width as long as height else makes height as long as width--> | |
<enum name="SQUARE" value="0"/> | |
<!--DEVICE - device width and height is calculated and those values are divided by divideWidthBy/divideHeightBy--> | |
<enum name="DEVICE" value="1"/> | |
<!--PARENT - parent's width and height is calculated and those values are divided by divideWidthBy/divideHeightBy--> | |
<enum name="PARENT" value="2"/> | |
</attr> | |
</declare-styleable> | |
</resources> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com..example; | |
import android.content.Context; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.util.AttributeSet; | |
/** | |
* Created by SachinR on 7/21/2017. | |
*/ | |
public interface CommonComponent { | |
/** | |
* Initialize this layout using the following parameters | |
* | |
* @param context current context of the application | |
* @param attrs attributes specified to view | |
*/ | |
void init(@NonNull Context context, @Nullable AttributeSet attrs); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.components; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.util.AttributeSet; | |
import android.view.View; | |
import android.widget.FrameLayout; | |
import com.example.R; | |
import com.example.CommonComponent; | |
import com.example.RatioType; | |
/** | |
* onMeasure() is your opportunity to tell Android how big you want your custom view to be dependent the layout constraints provided by the parent; it is also your custom view's opportunity to learn what those layout constraints are (in case you want to behave differently in a match_parent situation than a wrap_content situation). These constraints are packaged up into the MeasureSpec values that are passed into the method. Here is a rough correlation of the mode values: | |
* <p> | |
* <li>EXACTLY means the layout_width or layout_height value was set to a specific value. You should probably make your view this size. This can also get triggered when match_parent is used, to set the size exactly to the parent view (this is layout dependent in the framework).</li> | |
* <li>AT_MOST typically means the layout_width or layout_height value was set to match_parent or wrap_content where a maximum size is needed (this is layout dependent in the framework), and the size of the parent dimension is the value. You should not be any larger than this size.</li> | |
* <li>UNSPECIFIED typically means the layout_width or layout_height value was set to wrap_content with no restrictions. You can be whatever size you would like. Some layouts also use this callback to figure out your desired size before determine what specs to actually pass you again in a second measure request.</li> | |
* <br>The contract that exists with onMeasure() is that setMeasuredDimension() MUST be called at the end with the size you would like the view to be. This method is called by all the framework implementations, including the default implementation found in View, which is why it is safe to call super instead if that fits your use case. | |
* <p> | |
* Granted, because the framework does apply a default implementation, it may not be necessary for you to override this method, but you may see clipping in cases where the view space is smaller than your content if you do not, and if you lay out your custom view with wrap_content in both directions, your view may not show up at all because the framework doesn't know how large it is! | |
* <p> | |
* see {@link R.styleable#CustomFrameLayout CustomFrameLayout Attributes} | |
* <p> | |
* <p> | |
* <br> Created on 9/17/2017. | |
*/ | |
public class CustomFrameLayout extends FrameLayout implements CommonComponent { | |
@RatioType | |
private int RATIO_TYPE = RatioType.DEVICE; | |
private int deviceWidth; | |
private int deviceHeight; | |
private float widthDivider = -1f; | |
private float heightDivider = -1f; | |
private boolean isWidthPreferred = true; | |
public CustomFrameLayout(Context context) { | |
this(context, null); | |
} | |
public CustomFrameLayout(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
setSaveEnabled(true); | |
init(context, attrs); | |
} | |
@RatioType | |
private static int getRatioType(int ratioType) { | |
@RatioType | |
int RATIO_TYPE; | |
switch (ratioType) { | |
case 0: | |
RATIO_TYPE = RatioType.SQUARE; | |
break; | |
case 1: | |
RATIO_TYPE = RatioType.DEVICE; | |
break; | |
case 2: | |
RATIO_TYPE = RatioType.PARENT; | |
break; | |
default: | |
RATIO_TYPE = RatioType.DEVICE; | |
break; | |
} | |
return RATIO_TYPE; | |
} | |
@Override | |
public void init(@NonNull Context context, @Nullable AttributeSet attrs) { | |
deviceWidth = context.getResources().getDisplayMetrics().widthPixels; | |
deviceHeight = context.getResources().getDisplayMetrics().heightPixels; | |
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomFrameLayout); | |
try { | |
isWidthPreferred = a.getBoolean(R.styleable.CustomFrameLayout_isWidthPreferred, true); | |
heightDivider = a.getFloat(R.styleable.CustomFrameLayout_divideHeightBy, -1f); | |
widthDivider = a.getFloat(R.styleable.CustomFrameLayout_divideWidthBy, -1f); | |
int ratioType = a.getInt(R.styleable.CustomFrameLayout_RatioType, 1); | |
RATIO_TYPE = getRatioType(ratioType); | |
} finally { | |
a.recycle(); | |
} | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
if (getContext() == null || ((widthDivider == -1f || widthDivider == 1f) && (heightDivider == -1f || heightDivider == 1f))) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
return; | |
} | |
if (RATIO_TYPE == RatioType.SQUARE) { | |
if (isWidthPreferred) { | |
super.onMeasure(widthMeasureSpec, widthMeasureSpec); | |
} else { | |
super.onMeasure(heightMeasureSpec, heightMeasureSpec); | |
} | |
return; | |
} | |
int desiredWidth; | |
int desiredHeight; | |
if (RATIO_TYPE == RatioType.PARENT) { | |
View view; | |
try { | |
view = (View) getParent(); | |
int parentWidth = view.getWidth(); | |
int parentHeight = view.getHeight(); | |
desiredWidth = (int) (parentWidth / widthDivider); | |
desiredHeight = (int) (parentHeight / heightDivider); | |
} catch (Exception ex) { | |
desiredWidth = (int) (deviceWidth / widthDivider); | |
desiredHeight = (int) (deviceHeight / heightDivider); | |
} | |
} else { | |
desiredWidth = (int) (deviceWidth / widthDivider); | |
desiredHeight = (int) (deviceHeight / heightDivider); | |
} | |
int widthMode = MeasureSpec.getMode(widthMeasureSpec); | |
int widthSize = MeasureSpec.getSize(widthMeasureSpec); | |
int heightMode = MeasureSpec.getMode(heightMeasureSpec); | |
int heightSize = MeasureSpec.getSize(heightMeasureSpec); | |
int width; | |
int height; | |
//Measure Width | |
if (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST) { | |
//Can't be bigger than... | |
width = desiredWidth < 0 ? widthSize : Math.min(desiredWidth, widthSize); | |
} else { | |
//Be whatever you want | |
width = desiredWidth < 0 ? desiredWidth : widthSize; | |
} | |
//Measure Height | |
if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) { | |
//Can't be bigger than... | |
height = desiredHeight < 0 ? heightSize : Math.min(desiredHeight, heightSize); | |
} else { | |
//Be whatever you want | |
height = desiredHeight < 0 ? heightSize : desiredHeight; | |
} | |
super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), MeasureSpec.makeMeasureSpec(height, heightMode)); | |
} | |
@Override | |
public Parcelable onSaveInstanceState() { | |
Parcelable superState = super.onSaveInstanceState(); | |
SavedState ss = new SavedState(superState); | |
ss.RATIO_TYPE = RATIO_TYPE; | |
ss.deviceWidth = deviceWidth; | |
ss.deviceHeight = deviceHeight; | |
ss.widthDivider = widthDivider; | |
ss.heightDivider = heightDivider; | |
ss.isWidthPreferred = isWidthPreferred; | |
return ss; | |
} | |
@Override | |
public void onRestoreInstanceState(Parcelable state) { | |
SavedState ss = (SavedState) state; | |
super.onRestoreInstanceState(ss.getSuperState()); | |
RATIO_TYPE = ss.RATIO_TYPE; | |
deviceWidth = ss.deviceWidth; | |
deviceHeight = ss.deviceHeight; | |
widthDivider = ss.widthDivider; | |
heightDivider = ss.heightDivider; | |
isWidthPreferred = ss.isWidthPreferred; | |
} | |
private static class SavedState extends BaseSavedState { | |
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { | |
public SavedState createFromParcel(Parcel in) { | |
return new SavedState(in); | |
} | |
public SavedState[] newArray(int size) { | |
return new SavedState[size]; | |
} | |
}; | |
@RatioType | |
private int RATIO_TYPE = RatioType.DEVICE; | |
private int deviceWidth; | |
private int deviceHeight; | |
private float widthDivider = -1f; | |
private float heightDivider = -1f; | |
private boolean isWidthPreferred = true; | |
SavedState(Parcelable superState) { | |
super(superState); | |
} | |
private SavedState(Parcel in) { | |
super(in); | |
RATIO_TYPE = getRatioType(in.readInt()); | |
deviceWidth = in.readInt(); | |
deviceHeight = in.readInt(); | |
widthDivider = in.readFloat(); | |
heightDivider = in.readFloat(); | |
isWidthPreferred = in.readByte() != 0; | |
} | |
@Override | |
public void writeToParcel(Parcel out, int flags) { | |
super.writeToParcel(out, flags); | |
out.writeInt(RATIO_TYPE); | |
out.writeInt(deviceWidth); | |
out.writeInt(deviceHeight); | |
out.writeFloat(widthDivider); | |
out.writeFloat(heightDivider); | |
out.writeByte((byte) (isWidthPreferred ? 1 : 0)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.interfaces; | |
import android.support.annotation.IntDef; | |
import java.lang.annotation.Retention; | |
import static java.lang.annotation.RetentionPolicy.SOURCE; | |
/** | |
* Created on 9/26/2017. | |
*/ | |
@Retention(SOURCE) | |
@IntDef({RatioType.SQUARE, RatioType.DEVICE,RatioType.PARENT}) | |
public @interface RatioType { | |
int SQUARE = 0; | |
int DEVICE = 1; | |
int PARENT = 2; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CustomFrameLayout -
this layout specifies its own width/height based on the Device/Parent width/height.
For this to work we need to set few properties at xml time
1.) divideWidthBy -Divides device/parent width by specified value and applies the result to its own width
2.) divideHeightBy -Divides device/parent height by specified value and applies the result to its own height
3.) isWidthPreferred -When we square layout, we to make square based on with or base on height . Set false to have a square layout based on height priority
4.) RatioType -Division is performed based on the following types
Note:- To make this work we have o set the layouts width and height and to match_arent and specify above layout. else this will be treated as normal FrameLayout
Usage: