Skip to content

Instantly share code, notes, and snippets.

@longkai
Created March 27, 2015 10:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save longkai/c96005f1fa63102254d8 to your computer and use it in GitHub Desktop.
Save longkai/c96005f1fa63102254d8 to your computer and use it in GitHub Desktop.
A floating label view which shows some labels on top of the first child.
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 longkai
*
* The software shall be used for good, not evil.
*/
package com.example.app.layout;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import com.example.layout_tester.app.R;
/**
* A floating label view which shows some labels on top of the first child.
*
* You can drag the tag to adjust the tag' s location.
*
* @author longkai
*/
public class FloatingLabelLayout extends ViewGroup implements View.OnTouchListener {
private int mTouchSlop;
private int lastX;
private int lastY;
public FloatingLabelLayout(Context context) {
this(context, null);
}
public FloatingLabelLayout(Context context, AttributeSet attrs) {
super(context, attrs);
bootstrap(context, attrs, 0, 0);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public FloatingLabelLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
bootstrap(context, attrs, defStyleAttr, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FloatingLabelLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
bootstrap(context, attrs, defStyleAttr, defStyleRes);
}
private void bootstrap(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override protected void onAttachedToWindow() {
super.onAttachedToWindow();
final int count = getChildCount();
if (count > 1) {
for (int i = 1; i < count; i++) {
getChildAt(i).setOnTouchListener(this);
}
}
}
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
final int count = getChildCount();
if (count > 1) {
for (int i = 1; i < count; i++) {
getChildAt(i).setOnTouchListener(null);
}
}
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
if (count == 0) {
// if has no child, rely on system' s impl
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
// this view' s bound build on top of the first child
View view = getChildAt(0);
if (view.getVisibility() == GONE) {
// if the first view is gone, skip it
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
// amend padding
width += getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
// we' re done
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));
// measure the floating label(s)
for (int i = 1; i < count; i++) {
View label = getChildAt(i);
if (label.getVisibility() != GONE) {
measureChild(label, widthMeasureSpec, heightMeasureSpec);
}
}
}
}
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
// skip if non-changed
if (!changed) {
return;
}
View view = getChildAt(0);
// the main layout, if gone, skip layout
if (view.getVisibility() == GONE) {
return;
}
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
l += getPaddingLeft();
t += getPaddingTop();
view.layout(l, t, l + view.getMeasuredWidth(), t + view.getMeasuredHeight());
final int count = getChildCount();
for (int i = 1; i < count; i++) {
View label = getChildAt(i);
if (label.getVisibility() != GONE) {
// layout label(s)
LayoutParams lp = (LayoutParams) label.getLayoutParams();
// NOTE: we simply no check for the x and y, both should be range in [0, 1)
int x = (int) (lp.x * width);
int y = (int) (lp.y * height);
if (lp.centerOrientated) {
x -= label.getMeasuredWidth() / 2;
y -= label.getMeasuredHeight() / 2;
}
label.layout(l + x, t + y, l + x + label.getMeasuredWidth(), t + y + label.getMeasuredHeight());
}
}
}
@Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
int xDiff = x - lastX;
int yDiff = y - lastY;
if (Math.abs(xDiff) > mTouchSlop || Math.abs(yDiff) > mTouchSlop) {
v.layout(v.getLeft() + xDiff, v.getTop() + yDiff, v.getRight() + xDiff, v.getBottom() + yDiff);
lastX = x;
lastY = y;
}
break;
case MotionEvent.ACTION_CANCEL:
lastX = 0;
lastY = 0;
break;
case MotionEvent.ACTION_UP:
// handle click event
v.performClick();
break;
}
return true;
}
// NOTE: since we use (x, y) coordinate to layout the label, margin is useless
public static class LayoutParams extends ViewGroup.LayoutParams {
/** x pos in relative of the layout' s first child */
public float x;
/** y pos in relative of the layout' s first child */
public float y;
public boolean centerOrientated;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.FloatingLabelView_Layout);
x = array.getFraction(R.styleable.FloatingLabelView_Layout_x, 1, 1, 0f);
y = array.getFraction(R.styleable.FloatingLabelView_Layout_y, 1, 1, 0f);
centerOrientated = array.getBoolean(R.styleable.FloatingLabelView_Layout_centerOrientated, false);
array.recycle();
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment