一个自定义TextView可实现各种控件右上,左上等位置附带便签实现。
只需要一个类就可以完成以上实现
LabelView
public class LabelView extends TextView {
private float _offsetx;
private float _offsety;
private float _anchorx;
private float _anchory;
private float _angel;
private int _labelViewContainerID;
private Animation _animation = new Animation() {
protected void applyTransformation(float interpolatedTime, Transformation t) {
Matrix tran = t.getMatrix();
tran.postTranslate(_offsetx, _offsety);
tran.postRotate(_angel, _anchorx, _anchory);
}
};
public enum Gravity {
LEFT_TOP, RIGHT_TOP
}
public LabelView(Context context) {
this(context, null);
}
public LabelView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
@SuppressLint("NewApi")
public LabelView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
_animation.setFillBefore(true);
_animation.setFillAfter(true);
_animation.setFillEnabled(true);
}
private void init() {
if (!(getLayoutParams() instanceof ViewGroup.LayoutParams)) {
LayoutParams layoutParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
setLayoutParams(layoutParams);
}
// the default value
//setPadding(dip2Px(40), dip2Px(2), dip2Px(40), dip2Px(2));
_labelViewContainerID = -1;
setGravity(android.view.Gravity.CENTER);
setTextColor(Color.WHITE);
setTypeface(Typeface.DEFAULT_BOLD);
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
setBackgroundColor(Color.BLUE);
}
public void setTargetView(View target, int distance, Gravity gravity) {
if (!replaceLayout(target)) {
return;
}
final int d = dip2Px(distance);
final Gravity g = gravity;
final View v = target;
ViewTreeObserver vto = getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
calcOffset(getMeasuredWidth(), d, g, v.getMeasuredWidth(), false);
}
});
}
public void setTargetViewInBaseAdapter(View target, int targetWidth, int distance, Gravity gravity) {
if (!replaceLayout(target)) {
return;
}
//measure(0, 0);
//calcOffset(getMeasuredWidth(), distance, gravity, targetWidth, true);
calcOffset(dip2Px(targetWidth), distance, gravity, targetWidth, true);
}
public void remove() {
if (getParent() == null || _labelViewContainerID == -1) {
return;
}
ViewGroup frameContainer = (ViewGroup) getParent();
assert (frameContainer.getChildCount() == 2);
View target = frameContainer.getChildAt(0);
ViewGroup parentContainer = (ViewGroup) frameContainer.getParent();
int groupIndex = parentContainer.indexOfChild(frameContainer);
if (frameContainer.getParent() instanceof RelativeLayout) {
for (int i = 0; i < parentContainer.getChildCount(); i++) {
if (i == groupIndex) {
continue;
}
View view = parentContainer.getChildAt(i);
RelativeLayout.LayoutParams para = (RelativeLayout.LayoutParams) view.getLayoutParams();
for (int j = 0; j < para.getRules().length; j++) {
if (para.getRules()[j] == _labelViewContainerID) {
para.getRules()[j] = target.getId();
}
}
view.setLayoutParams(para);
}
}
ViewGroup.LayoutParams frameLayoutParam = frameContainer.getLayoutParams();
target.setLayoutParams(frameLayoutParam);
parentContainer.removeViewAt(groupIndex);
frameContainer.removeView(target);
frameContainer.removeView(this);
parentContainer.addView(target,groupIndex);
_labelViewContainerID = -1;
}
@SuppressLint("NewApi")
private boolean replaceLayout(View target) {
if (getParent() != null || target == null || target.getParent() == null || _labelViewContainerID != -1) {
return false;
}
ViewGroup parentContainer = (ViewGroup) target.getParent();
if (target.getParent() instanceof FrameLayout) {
((FrameLayout) target.getParent()).addView(this);
} else if (target.getParent() instanceof ViewGroup) {
int groupIndex = parentContainer.indexOfChild(target);
_labelViewContainerID = generateViewId();
// relativeLayout need copy rule
if (target.getParent() instanceof RelativeLayout) {
for (int i = 0; i < parentContainer.getChildCount(); i++) {
if (i == groupIndex) {
continue;
}
View view = parentContainer.getChildAt(i);
RelativeLayout.LayoutParams para = (RelativeLayout.LayoutParams) view.getLayoutParams();
for (int j = 0; j < para.getRules().length; j++) {
if (para.getRules()[j] == target.getId()) {
para.getRules()[j] = _labelViewContainerID;
}
}
view.setLayoutParams(para);
}
}
parentContainer.removeView(target);
// new dummy layout
FrameLayout labelViewContainer = new FrameLayout(getContext());
ViewGroup.LayoutParams targetLayoutParam = target.getLayoutParams();
labelViewContainer.setLayoutParams(targetLayoutParam);
target.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// add target and label in dummy layout
labelViewContainer.addView(target);
labelViewContainer.addView(this);
labelViewContainer.setId(_labelViewContainerID);
// add dummy layout in parent container
parentContainer.addView(labelViewContainer, groupIndex, targetLayoutParam);
}
return true;
}
private void calcOffset(int labelWidth, int distance, Gravity gravity, int targetWidth, boolean isDP) {
int d = dip2Px(distance);
int tw = isDP ? dip2Px(targetWidth) : targetWidth;
float edge = (float) ((labelWidth - 2 * d) / (2 * 1.414));
if (gravity == Gravity.LEFT_TOP) {
_anchorx = -edge;
_offsetx = _anchorx;
_angel = -45;
} else if (gravity == Gravity.RIGHT_TOP) {
_offsetx = tw + edge - labelWidth;
_anchorx = tw + edge;
_angel = 45;
}
_anchory = (float) (1.414 * d + edge);
_offsety = _anchory;
clearAnimation();
startAnimation(_animation);
}
private int dip2Px(float dip) {
return (int) (dip * getContext().getResources().getDisplayMetrics().density + 0.5f);
}
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
public static int generateViewId() {
for (; ; ) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
}
整体布局的位置,左上以及右上,如果需要其他位置可自行添加
public enum Gravity {
LEFT_TOP, RIGHT_TOP
}
初始化参数:
字体大小:setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
背景颜色:setBackgroundColor(Color.BLUE);
字体颜色:setTextColor(Color.WHITE);
位置:setGravity(android.view.Gravity.CENTER);
字体风格:setTypeface(Typeface.DEFAULT_BOLD);
一般控件设置用
public void setTargetView(View target, int distance, Gravity gravity)
适配器控件设置用
public void setTargetViewInBaseAdapter(View target, int targetWidth, int distance, Gravity gravity)
让标签消失
public void remove()
MainActivity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
{
final LabelView label = new LabelView(this);
//涂鸦部分文字的内容
label.setText("大长腿");
//涂鸦部分的颜色
label.setBackgroundColor(0x0EfE32E20);
// public void setTargetView(View target, int distance, Gravity gravity)
//3个参数,一个是控件的ID,填充带的长度或者说便宜的距离(反正越大那一条东西越长),填充带的位置
label.setTargetView(findViewById(R.id.image1), 30,
LabelView.Gravity.LEFT_TOP);
//监听事件
findViewById(R.id.image1).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
//填充带消失
label.remove();
//吐司内容
Toast.makeText(MainActivity.this,
"大长腿消失了", Toast.LENGTH_SHORT)
.show();
}
});
}
{
//效果同上,只是涂鸦层位置的变化
final LabelView label = new LabelView(this);
label.setText("绝对领域");
label.setBackgroundColor(0xff491E23);
label.setTargetView(findViewById(R.id.image2), 22,
LabelView.Gravity.RIGHT_TOP);
findViewById(R.id.image2).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
label.remove();
Toast.makeText(MainActivity.this,
"绝对领域消失了", Toast.LENGTH_SHORT)
.show();
}
});
}
{
//Button也适用
LabelView label = new LabelView(this);
label.setText("按钮");
label.setBackgroundColor(0xffE91E63);
//位于右上角
label.setTargetView(findViewById(R.id.button), 14,
LabelView.Gravity.RIGHT_TOP);
findViewById(R.id.button).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "button click",
Toast.LENGTH_SHORT).show();
}
});
}
{
//TextView适用
LabelView label = new LabelView(this);
label.setText("Text");
label.setBackgroundColor(0xff03a9f4);
label.setTargetView(findViewById(R.id.text), 11,
LabelView.Gravity.LEFT_TOP);
findViewById(R.id.text).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,
"please click ListView Demo",
Toast.LENGTH_SHORT).show();
}
});
}
{
LabelView label = new LabelView(this);
label.setText("List");
label.setBackgroundColor(0xff03a9f4);
label.setTargetView(findViewById(R.id.click), 8,
LabelView.Gravity.RIGHT_TOP);
findViewById(R.id.click).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this,
ListViewActivity.class);
startActivity(i);
}
});
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
}
布局文件:
<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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button android:id="@+id/button" android:layout_width="200dp" android:layout_height="48dp" android:background="#03a9f4" android:text="按钮" android:textColor="#ffffff" />
<LinearLayout android:layout_marginTop="24dp" android:layout_width="match_parent" android:layout_height="wrap_content">
<ImageView android:id="@+id/image1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerCrop" android:src="@mipmap/image1" />
<ImageView android:id="@+id/image2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerCrop" android:src="@mipmap/image2" />
</LinearLayout>
<TextView android:id="@+id/text" android:layout_marginTop="24dp" android:layout_width="wrap_content" android:padding="16dp" android:background="#212121" android:layout_gravity="center" android:gravity="center" android:text="TextView" android:textColor="#ffffff" android:layout_height="48dp" />
<Button android:id="@+id/click" android:layout_marginTop="20dp" android:layout_width="200dp" android:layout_gravity="center_horizontal" android:layout_height="48dp" android:background="#E91E63" android:text="点击进入ListView" android:textColor="#ffffff" />
</LinearLayout>
ListViewActivity
public class ListViewActivity extends Activity {
public class CategoryData {
public String image;
public String text;
public String label;
}
public class CategoryAdapter extends SimpleBaseAdapter<CategoryData> {
public CategoryAdapter(Context context, List<CategoryData> data) {
super(context, data);
}
@Override
public int getItemResource() {
return R.layout.list_view_item;
}
@Override
public View getItemView(int position, View convertView, ViewHolder holder) {
CategoryData item = (CategoryData) _categoryAdapter.getItem(position);
TextView textView = holder.getView(R.id.text);
textView.setText(item.text);
ImageView imageView = holder.getView(R.id.image);
imageView.setImageResource(Integer.parseInt(item.image));
// you have to generate label ID manual
LabelView label = holder.getView(12345);
if (label == null) {
label = new LabelView(ListViewActivity.this);
label.setId(12345);
label.setBackgroundColor(0xffE91E63);
// public void setTargetViewInBaseAdapter(View target, int targetWidth, int distance, Gravity gravity)
//传入4个参数,被画的控件,label中text的位置(数字越小越靠右),整个标签便宜的位置,总体所在的区域
label.setTargetViewInBaseAdapter(imageView, 108, 25, LabelView.Gravity.LEFT_TOP);
}
label.setText(item.label);
return convertView;
}
}
private CategoryAdapter _categoryAdapter;
private ListView _listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
_listView = (ListView) findViewById(R.id.list_view);
_categoryAdapter = new CategoryAdapter(this, null);
_listView.setAdapter(_categoryAdapter);
//重复三轮妹子
getCategoryData();
getCategoryData();
getCategoryData();
}
private void getCategoryData() {
List<CategoryData> data = new ArrayList<CategoryData>();
{
CategoryData item = new CategoryData();
item.text = "妹子好看";
item.image = R.mipmap.k1 + "";
item.label = "妹子1";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "绝对领域";
item.image = R.mipmap.k2 + "";
item.label = "妹子2";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "妹子好看1";
item.image = R.mipmap.k3 + "";
item.label = "妹子3";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "绝对领域1";
item.image = R.mipmap.k4 + "";
item.label = "妹子4";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "啪啪啪";
item.image = R.mipmap.k5 + "";
item.label = "妹子5";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "萌萌哒,呵呵哒";
item.image = R.mipmap.k6 + "";
item.label = "妹子6";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "肉便器噼里啪啦";
item.image = R.mipmap.k7 + "";
item.label = "妹子7";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "稀里哗啦";
item.image = R.mipmap.k8 + "";
item.label = "妹子8";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "咖喱给给";
item.image = R.mipmap.k9 + "";
item.label = "妹子9";
data.add(item);
}
{
CategoryData item = new CategoryData();
item.text = "呵呵哈hi";
item.image = R.mipmap.k10 + "";
item.label = "妹子10";
data.add(item);
}
_categoryAdapter.addAll(data);
_categoryAdapter.notifyDataSetChanged();
}
}
其他一些只是为了实现而写了,可以直接看源码
源码地址:http://yunpan.cn/cmxxzBp8T8MSX 访问密码 7dcc