您的位置:首页 > 移动开发 > Android开发

android 自定义LabelView实现各类小标签,重要功能已标注

2015-09-02 10:13 756 查看

转载请注明出处:王亟亟的大牛之路

一个自定义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

部分内容参考网上,如有雷同,经供参考
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: