本文共 16967 字,大约阅读时间需要 56 分钟。
一个自定义TextView可实现各种控件右上,左上等位置附带便签实现。
项目结构:
运行效果:
只需要一个类就可以完成以上实现
LabelViewpublic 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; }}
布局文件:
ListViewActivity
public class ListViewActivity extends Activity { public class CategoryData { public String image; public String text; public String label; } public class CategoryAdapter extends SimpleBaseAdapter{ public CategoryAdapter(Context context, List 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 data = new ArrayList (); { 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(); }}
其他一些只是为了实现而写了,可以直接看源码
源码地址: 访问密码 7dcc