Created
March 27, 2015 10:26
-
-
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.
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
/* | |
* 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