我们有时候,需要在已经设计好的界面上的某一个View上,在事后添加一些view. 如一个消息到达的时候,在切换到消息的那个空间上面,显示一个消息数量的图标。
在开源的项目 https://github.com/jgilfelt/android-viewbadger 中,实现了这样的效果。见下图
在上图的Button 中,上面的红色的提示的图标不是在布局中添加的,而是通过BadgeView 这个类实现的通过原始的控件,动态的添加到这个界面上去的。
部分源码的分析载录.
public BadgeView(Context context) { this(context, (AttributeSet) null, android.R.attr.textViewStyle); } public BadgeView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.textViewStyle); } /** * Constructor - * * create a new BadgeView instance attached to a target {@link android.view.View}. * * @param context context for this view. * @param target the View to attach the badge to. */ public BadgeView(Context context, View target) { this(context, null, android.R.attr.textViewStyle, target, 0); } /** * Constructor - * * create a new BadgeView instance attached to a target {@link android.widget.TabWidget} * tab at a given index. * * @param context context for this view. * @param target the TabWidget to attach the badge to. * @param index the position of the tab within the target. */ public BadgeView(Context context, TabWidget target, int index) { this(context, null, android.R.attr.textViewStyle, target, index); } public BadgeView(Context context, AttributeSet attrs, int defStyle) { this(context, attrs, defStyle, null, 0); } public BadgeView(Context context, AttributeSet attrs, int defStyle, View target, int tabIndex) { super(context, attrs, defStyle); init(context, target, tabIndex); } private void init(Context context, View target, int tabIndex) { this.context = context; this.target = target; this.targetTabIndex = tabIndex; // apply defaults badgePosition = DEFAULT_POSITION; badgeMarginH = dipToPixels(DEFAULT_MARGIN_DIP); badgeMarginV = badgeMarginH; badgeColor = DEFAULT_BADGE_COLOR; setTypeface(Typeface.DEFAULT_BOLD); int paddingPixels = dipToPixels(DEFAULT_LR_PADDING_DIP); setPadding(paddingPixels, 0, paddingPixels, 0); setTextColor(DEFAULT_TEXT_COLOR); fadeIn = new AlphaAnimation(0, 1); fadeIn.setInterpolator(new DecelerateInterpolator()); fadeIn.setDuration(200); fadeOut = new AlphaAnimation(1, 0); fadeOut.setInterpolator(new AccelerateInterpolator()); fadeOut.setDuration(200); isShown = false; if (this.target != null) { applyTo(this.target); } else { show(); } } private void applyTo(View target) { // 1、获取目标源码的布局 LayoutParams lp = target.getLayoutParams(); ViewParent parent = target.getParent(); /** * 新建个容器 存放 原来的target 和 即将添加的 通知信息的TextView */ FrameLayout container = new FrameLayout(context); if (target instanceof TabWidget) { // set target to the relevant tab child container target = ((TabWidget) target).getChildTabViewAt(targetTabIndex); this.target = target; ((ViewGroup) target).addView(container, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); this.setVisibility(View.GONE); container.addView(this); } else { // TODO verify that parent is indeed a ViewGroup //获取target View的 所在的容器 ViewGroup group = (ViewGroup) parent; //获取target View的 所在的容器中的位置 int index = group.indexOfChild(target); //target View的 所在的容器 将 target View 移除 group.removeView(target); // 将新建的 即将包好 target View 和 badge view(在此处是,这个 通知的TextView) 所在的容器,添加到原来target view 所在的位置 group.addView(container, index, lp); /** * 在自己新建的容器中间 添加target view 和自己 新定义的view. */ container.addView(target); this.setVisibility(View.GONE); container.addView(this); // 界面刷新 group.invalidate(); } }
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); final TextView text = (TextView) findViewById(R.id.test); Button button = (Button) findViewById(R.id.button); final BadgeView badgeView=new BadgeView(this, text); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { badgeView.toggle(); } }); } }
public class BadgeView { private Context mContext; private View targeView; TextView textView; public BadgeView(Context mContext, View targeView) { super(); this.mContext = mContext; this.targeView = targeView; // 1、获取目标源码的布局 LayoutParams lParams = targeView.getLayoutParams(); ViewParent targetViewParent = targeView.getParent(); /** * 新建个容器 存放 原来的target 和 即将添加的 通知信息的TextView */ FrameLayout container=new FrameLayout(mContext); //获取target View的 所在的容器 ViewGroup targetViewParentGroup=(ViewGroup) targetViewParent; //获取target View的 所在的容器中的位置 int index=targetViewParentGroup.indexOfChild(targeView); //target View的 所在的容器 将 target View 移除 targetViewParentGroup.removeView(targeView); // 将新建的 即将包好 target View 和 badge view(在此处是,这个 通知的TextView) 所在的容器,添加到原来target view 所在的位置 targetViewParentGroup.addView(container, index, lParams); /** * 在自己新建的容器中间 添加target view 和自己 新定义的view. */ textView=new TextView(mContext); textView.setText("sss"); textView.setTextColor(Color.RED); textView.setVisibility(View.INVISIBLE); container.addView(targeView); container.addView(textView); // 界面刷新 targetViewParentGroup.invalidate(); } public void show() { textView.setVisibility(View.VISIBLE); } public void hide() { textView.setVisibility(View.INVISIBLE); } boolean isShow=false; public void toggle(){ if (isShow) { hide(); isShow=false; }else { show(); isShow=true; } } }
<RelativeLayout 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: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="com.example.myviewbadger.MainActivity$PlaceholderFragment" > <TextView android:id="@+id/test" android:layout_width="300dp" android:layout_height="200dp" android:background="@android:color/darker_gray" android:text="@string/hello_world" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="ss" /> </RelativeLayout>
https://github.com/jgilfelt/android-viewbadger