安卓问题-UI相关

UI相关问题

RecyclerView相关

recyclerView没有数据问题

RecyclerView.setLayoutManager 不设置这个 就是不行的 就是没有数据的

RecyclerView.getChildAt(position)空指针

问题:程序中,屏幕可以获取到 6 个子 view,当 getChildAt(position)的position 为 6 或 7 的时候,会有空指针异常

原因:getChildAt()只能获取到屏幕上显示的部分

解决:View childView = recyclerView.getLayoutManager().findViewByPosition(position);

RecyclerView局部刷新,其他控件会闪动

  1. 怀疑是宽高重新计算导致的,但写死宽高页面还是会闪动

  2. 怀疑更新到了这个ImageView,然后glide又重新加载了一次图片

    在xml中写入android:src:"@mipmap/ic_launcher"还会闪动,排除2

  3. 怀疑刷新到了ImageView,这个item渲染了一次。

    源码中:默认传入的是null,表示全部刷新

    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // since onItemRangeChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }
    

    解决:

    调用更改:

    //调用时,换个带payload参数的
    notifyItemChanged(position, 1);//这个1是随意的,其他值也可以
    

    adapter更改:(增加一个onBindViewHolder,带payloads参数)

    @Override
    public void onBindViewHolder(TestDownLoadHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            //走原来的逻辑
            onBindViewHolder(holder, position);
        } else {
            //有payloads的时候新的逻辑
            final AppInfoBean appInfoBean = data.get(position);
            if (appInfoBean != null) {
                holder.mPbDownProgress.setProgress(appInfoBean.getProgress());
                holder.mBtDownLoad.setText(appInfoBean.getDownloadPerSize());
            }
        }
    }
    
    @Override
    public void onBindViewHolder(final TestDownLoadHolder holder, final int position) {
    	//原来的逻辑
    }
    

RecyclerView嵌套滑动冲突

解决方案一:

父recyclerView拦截并消耗了点击事件,那么就不要让父recyclerView拦截呗
自定义父recyclerView并重写onInterceptTouchEvent()方法

public class ParentRecyclerView extends RecyclerView{
    public ParentRecyclerView(@NonNull Context context){
        super(context);
    }
    public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs){
        super(context, attrs);
    }
    public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle){
        super(context, attrs, defStyle);
    }
    //不拦截,继续分发下去
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e){
        return false;
    }
}

解决方案二:

子布局通知父布局不要拦截事件,通过requestDisallowInterceptTouchEvent方法干预事件分发过程
重写dispatchTouchEvent()方法,通知通知父层ViewGroup不要拦截点击事件

public ChildPresenter extends RecyclerView{
    public ChildPresenter(@NonNull Context context){
        super(context);
    }
    public ChildPresenter(@NonNull Context context, @Nullable AttributeSet attrs){
        super(context, attrs);
    }
    public ChildPresenter(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle){
        super(context, attrs, defStyle);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev){
        //父层ViewGroup不要拦截点击事件
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
}

解决方案三:

通过事件分发规则我们知道,OnTouchListener优先级很高,可以通过这个来告诉父布局,不要拦截我的事件

holder.recyclerView.setOnTouchListener{ v, event ->
     when(event.action){
         //当用户按下的时候,我们告诉父组件,不要拦截我的事件(这个时候子组件是可以正常响应事件的),拿起之后就会告诉父组件可以阻止。
         MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> v.parent.requestDisallowInterceptTouchEvent(true)
         MotionEvent.ACTION_UP -> v.parent.requestDisallowInterceptTouchEvent(false)
     }
     false
}

ListView相关

ListView的setOnItemClickListener无效

问题:使用listview的时候遇到了设置item监听事件的时候在没有回调onItemClick 方法的问题

我的情况是在item中有一个Button按钮,所以不会回调。

解决:

法1. 在checkbox、button对应的view处加

android:focusable=”false”
android:clickable=”false”
android:focusableInTouchMode=”false”

法2. 在item最外层添加属性

android:descendantFocusability=”blocksDescendants”

原因:当listview中包含button,checkbox等控件的时候,android会默认将focus给了这些控件,也就是说listview的item根本就获取不到focus,所以导致onitemclick时间不能触发

ArrayIndexOutOfBoundsException with custom Android Adapter for multiple views in ListView

listView有多种布局,可能是getItemViewType() >= getViewTypeCount() 导致的(检查getViewTypeCount()的值是不是写死了)

ListView滑动到标题栏、导航栏上

现象:出现滑动ListView滑动到标题栏上、导航栏上。点击标题栏上tab(多tab页面)此tab会刷新成原来没有被覆盖的样子。

寻找问题的经历的步骤:

1、猜测是 PagerSlidingTabStrip(PagerSlidingTabStrip+Viewpager+Fragment)导致的,故替换成TabLlayout+ViewPager+Fragment来实现。结果还是不行

ListView滑动到标题栏、导航栏上1.png

2、替换Activity、Adapter,只留item布局不一样,发现问题依然存在。所以定位问题是出在item布局上。查看了item布局发现有

ListView滑动到标题栏、导航栏上2.png

替换了com.u1city.androidframe.customView.FilletFrameLayout为RelativeLayout发现正常了。但是圆角没有了,所以自定义圆角RelativeLayout,但是问题又出现了。所以定位到问题是圆角导致的。

最终版本:最外层用RelativeLayout,里面的圆角再通过别的方式来实现。

ListView滑动到标题栏、导航栏上3.png

ListView滑动到标题栏、导航栏上4.png

ListView套多个Editext和多个RatingBar错乱问题

解决代码:

public class ToEvaluateAdapter extends BaseAdapter {

    private LayoutInflater inflater;
    private List<CommentInfo> list;

    public ToEvaluateAdapter(Context context, List<CommentInfo> list) {
        this.list = list;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int arg0) {
        return list == null ? null : list.get(arg0);
    }

    @Override
    public long getItemId(int arg0) {
        return arg0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.home_toevaluate_listitem, null);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        final CommentInfo commentInfo = list.get(position);

        /*--------------------------------------EditText -begin-----------------------------------------*/
        //把CommentInfo与EditText进行绑定
        viewHolder.home_toevaluate_content_edt.setTag(commentInfo);

//        清除焦点
        viewHolder.home_toevaluate_content_edt.clearFocus();
        viewHolder.home_toevaluate_content_edt.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //存储变化
                CommentInfo commentInfo = (CommentInfo) viewHolder.home_toevaluate_content_edt.getTag();
                commentInfo.setContent(s + "");
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        //大部分情况下,getview中有if必须有else
        if (!TextUtils.isEmpty(commentInfo.getContent())) {
            viewHolder.home_toevaluate_content_edt.setText(commentInfo.getContent());
        } else {
            viewHolder.home_toevaluate_content_edt.setText("");//没有数值时,这句不能省略,否则会导致别的item展示当前的数据
            viewHolder.home_toevaluate_content_edt.setHint("说说哪里满意,帮助大家选择");
        }
        /*--------------------------------------EditText -end-----------------------------------------*/

        /*--------------------------------------RatingBar-begin-----------------------------------*/
        //方法一: 给RatingBar设置标记
//        viewHolder.ratingBar.setTag(position);
        //方法二: 把CommentInfo与RatingBar进行绑定
        viewHolder.ratingBar.setTag(commentInfo);
        // 清除焦点
        viewHolder.ratingBar.clearFocus();
        //滑动星星的时候
        viewHolder.ratingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
            @Override
            public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
                //存储变化
//          方法一:获取标记的数据      CommentInfo commentInfo = getCommentInfo((Integer) ratingBar.getTag());
//                方法二:获取标记的数据
                CommentInfo commentInfo = (CommentInfo) viewHolder.ratingBar.getTag();
                commentInfo.setGrade(rating);//不要在这进行 *2 的操作,否则会导致每次的数值都翻倍,最终使RatingBar充满,只能在展示的时候进行 *2 展示

                if (commentInfo.getGrade() == 0) {
                    viewHolder.home_toevaluate_value_tv.setText("请滑动星星评分");
                    viewHolder.home_toevaluate_value_tv.setTextColor(Color.BLACK);
                } else {
                    viewHolder.home_toevaluate_value_tv.setText(commentInfo.getGrade() * 2 + "");
                    viewHolder.home_toevaluate_value_tv.setTextColor(Color.GREEN);
                }
            }
        });

        if (commentInfo.getGrade() == 0) {
            viewHolder.home_toevaluate_value_tv.setText("请滑动星星评分");
            viewHolder.home_toevaluate_value_tv.setTextColor(Color.BLACK);
        } else {
            viewHolder.home_toevaluate_value_tv.setText(commentInfo.getGrade() * 2 + "");
            viewHolder.home_toevaluate_value_tv.setTextColor(Color.GREEN);
        }

        viewHolder.ratingBar.setRating(commentInfo.getGrade());
        /*--------------------------------------RatingBar-end-----------------------------------*/

        return convertView;
    }

    //方法二:用于取出集合中的数据
    private CommentInfo getCommentInfo(int position) {
        return list.get(position);
    }

    class ViewHolder {
        RatingBar ratingBar;
        TextView home_toevaluate_value_tv;
        EditText home_toevaluate_content_edt;

        public ViewHolder(View convertView) {
            ratingBar = (RatingBar) convertView.findViewById(R.id.home_toevaluate_ratingBar);
            home_toevaluate_value_tv = (TextView) convertView.findViewById(R.id.home_toevaluate_value_tv);
            home_toevaluate_content_edt = (EditText) convertView.findViewById(R.id.home_toevaluate_content_edt);
        }
    }
    
    //数据对外的接口
    public List<CommentInfo> getList() {
        return list;
    }
}

在ListView历史复用中Edittext数据显示混乱

解决在ListView历史复用中Edittext数据显示混乱

//大部分情况下,getview中有if必须有else
if(!TextUtils.isEmpty(bean.getInput())){
    vh.mEditText.setText(bean.getInput());
}else{
    vh.mEditText.setText("");
}

自己挖的坑,为空的时候设置了setHintText没有设置setText导致其他item项的EditText也会显示当前item的值

解决在ListView历史复用中Edittext数据显示混乱

ExpandListView

ExpandListView滑动过程中发现显示错位问题

过程中出现错位问题:隐藏区域上划时显示出图片,本应该显示文字。

发现错误原因:

View childView =convertView;

View childView1 =convertView;

getGroupView中写法为:
ChildHolder childHolder;
if (groupPosition == 0) {
    View childView =convertView;
    if (childView == null) {
        childView = LayoutInflater.from(context).inflate(R.layout.item_hdly_child1, null);
        childHolder = new ChildHolder();
        //...
        childView.setTag(childHolder);
    } else {
        childHolder = (ChildHolder) childView.getTag();
    }
    //...
    convertView = childView;
}else {
    View childView1 =convertView;
    if (childView1 == null){
        childHolder = new ChildHolder();
        childView1 = LayoutInflater.from(context).inflate(R.layout.item_hdly_child2, null);
        //...
        childView1.setTag(childHolder);
    }else{
        childHolder = (ChildHolder) childView1.getTag();
    }
    //...
    convertView = childView1;
}

正确源码:

package rongshanghui.tastebychance.com.rongshanghui.zwdt.hdly;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

import rongshanghui.tastebychance.com.rongshanghui.R;
import rongshanghui.tastebychance.com.rongshanghui.util.NoDoubleClickListener;
import rongshanghui.tastebychance.com.rongshanghui.util.PicassoUtils;
import rongshanghui.tastebychance.com.rongshanghui.zwdt.hdly.bean.HDLYBean;

/**
 * Created by shenbinghong on 2018/1/16.
 */

public class ExpandListViewAdapter extends BaseExpandableListAdapter {
    private Context context;
//    private LayoutInflater inflater = null;
    private List<HDLYBean> groups;
    public ExpandListViewAdapter(Context context, List<HDLYBean> groups) {
//        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.context = context;
        this.groups = groups;
    }

    @Override
    public int getGroupCount() {
        return groups.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return groups.get(groupPosition).getItems().size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return groups.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return groups.get(groupPosition).getItems().get(childPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
//        return childPosition;
        return getCombinedChildId(groupPosition, childPosition);
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        GroupHolder groupHolder;
        View groupView = convertView;
        if (groupView == null) {
            groupView = LayoutInflater.from(context).inflate(R.layout.item_hdly_group, null);
            groupHolder = new GroupHolder();
            groupHolder.nameTv = (TextView) groupView.findViewById(R.id.item_hdly_group_tv);
            groupView.setTag(groupHolder);
        }else{
            groupHolder = (GroupHolder) groupView.getTag();
        }

        groupHolder.nameTv.setText(groups.get(groupPosition).getName());
        return groupView;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        ChildHolder childHolder;
        if (groupPosition == 0) {
            View childView = null;
            if (childView == null) {
                childView = LayoutInflater.from(context).inflate(R.layout.item_hdly_child1, null);
                childHolder = new ChildHolder();
                childHolder.imgIv = (ImageView) childView.findViewById(R.id.hdly_child1__bg_iv);
                childHolder.ckhfTv = (TextView) childView.findViewById(R.id.hdly_child1_ckhf_tv);
                childHolder.lyTv = (TextView) childView.findViewById(R.id.hdly_child1_ly_tv);
                childHolder.unreadTv = (TextView) childView.findViewById(R.id.unread);
                childView.setTag(childHolder);
            } else {
                childHolder = (ChildHolder) childView.getTag();
            }

            if (childPosition == 0) {
                PicassoUtils.getinstance().loadNormalByPath(context, groups.get(groupPosition).getItems().get(childPosition).getAvator(), childHolder.imgIv);
                if (groups.get(groupPosition).getItems().get(childPosition).getHuifuNum() > 0) {
                    childHolder.unreadTv.setVisibility(View.VISIBLE);
                } else {
                    childHolder.unreadTv.setVisibility(View.INVISIBLE);
                }
                childHolder.ckhfTv.setOnClickListener(new NoDoubleClickListener() {
                    @Override
                    public void onNoDoubleClick(View v) {

                    }
                });
                childHolder.lyTv.setOnClickListener(new NoDoubleClickListener() {
                    @Override
                    public void onNoDoubleClick(View v) {

                    }
                });
            }

            convertView = childView;
        }else {
            View childView1 = null;
            if (childView1 == null){
                childHolder = new ChildHolder();
                childView1 = LayoutInflater.from(context).inflate(R.layout.item_hdly_child2, null);
                childHolder.nameTv = (TextView) childView1.findViewById(R.id.hdly_child2_name_tv);
                childHolder.timeTv = (TextView) childView1.findViewById(R.id.hdly_child2_time_tv);
                childView1.setTag(childHolder);
            }else{
                childHolder = (ChildHolder) childView1.getTag();
            }

            if (childHolder.nameTv != null){
                childHolder.timeTv.setText(groups.get(groupPosition).getItems().get(childPosition).getTime());
            }
            if (childHolder.timeTv != null) {
                childHolder.nameTv.setText(groups.get(groupPosition).getItems().get(childPosition).getName());
            }

            convertView = childView1;
        }

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        // 根据方法名,此处应该表示二级条目是否可以被点击   先返回true
        return true;
    }

    class GroupHolder{
        private TextView nameTv;
    }

    class ChildHolder{
        private ImageView imgIv;
        private TextView ckhfTv,unreadTv,lyTv;

        private TextView nameTv;
        private TextView timeTv;
    }
}

NestedScrollView相关

问题:NestedScrollView嵌套 RecyclerView,页面载入时总是滑到最底部

原因:由于 NestedScrollView 嵌套 RecyclerView,RecyclerView 占据焦点会导致 NestedScrollView 内容上滑

解决:

方法一:

在根布局设置android:descendantFocusability="blocksDescendants"

其中android:descendantFocusability有三种值:

  • beforeDescendants:viewGroup 会优先其子类控件而获取到焦点
  • afterDescendants:viewGroup 只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants:viewGroup会覆盖子类控件而直接获得焦点

这种方法会造成页面中 EditText 焦点被抢导致无法输入,需要用第二种方法。

方法二:

android:focusable="true"
android:focusableInTouchMode="true"

方法三:

NestedScrollView顶部第一个控件使用:

android:focusable="true"
android:focusableInTouchMode="true"

这种方法不太可靠,因为有时可行,有时不可行。

Called attach on a child which is not detached: ViewHolder

问题:java.lang.IllegalArgumentException: Called attach on a child which is not detached: ViewHolder

我是在使用RecyclerView更新了某个item的数据,调用notifyItemChanged()的时候出现的这个问题。具体也没有报是哪一行的问题,根据错误提示,意思是操作的这个viewholder当前不是被绑定的,因为RecyclerView有缓存机制,未在屏幕上显示的item会被暂时回收,即detached。

1、出现这个问题的原因是更新了不在屏幕中显示的item,解决办法是判断要更新的item是不是在屏幕中,判断方法是获取RecyclerView的LayoutManager,前提是RecyclerView设置的LayoutManager是LinearLayoutManager。获取第一个可见位置和最后一个可见位置的position,判断当前要更新的item的position在这个范围内才更新。

LinearLayoutManager linearManager = (LinearLayoutManager) recyclerView.getLayoutManager();
//最后一个可见view的位置
int mLastVisibleItemPosition = linearManager.findLastVisibleItemPosition();
//第一个可见view的位置
int mFirstVisibleItemPosition = linearManager.findFirstVisibleItemPosition();

2、但是我的列表只有一条数据,并且是在屏幕中显示的,我在notifyItemChanged(0)还是报了这个错,经过仔细研究代码,我发现我的RecyclerView添加了Header,这样position=0的位置其实是header,所以出现了这样的问题。所以更新List中position位置的数据后要更新的Adapter的位置是position+1,改成notifyItemChanged(1)之后问题解决。

spinner相关

问题:自定义的适配

自定义的适配器问题:spinner在xml中需要记住设备background为null 才可以显示出下拉标志

如果打算修改文字离下拉标志的距离可以通过设置在适配器中的padding来设置。因为这里的设置会直接影响默认位置的距离

Dialog相关

Dialog上使用加载框progressdialog不显示

Dialog改变message无效的问题

//关于加载框或者是dialogfragment,中途改变message消息无效的问题:
//参考如下写法,验证有效:
mConnectResultTv.setText(mess);
mConnectResultTv.invalidate();
mConnectResultTv.postDelayed(new Runnable() {
	@Override
  public void run() {
  	dismiss();
  }
},1000);

dialog不居中的问题

Window dialogWindow = mProgressDlg.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
dialogWindow.setGravity(Gravity.CENTER);
//去除tile布局     mProgressDlg.requestWindowFeature(Window.FEATURE_NO_TITLE);
mProgressDlg.setContentView(R.layout.dialog_progress);

点9图相关

问题:点9图片导致其上图片显示未全屏问题

问题描述:

在app启动之后出现了启动页,在启动页上方会覆盖一张广告图。而广告图显示异常:只显示在左上角(屏幕2190x1900而广告图大小431x91)

处理过程:

  1. 断点得知是这个广告图所在的ImageView的大小 getWidth()和getMeasureWidth()都是431,尝试在此ImageView前面动态设置大小:用了重新设置setLayoutParam,无效

  2. 比对上一版的apk是正常,尝试从svn上找动到的文件➡️找到了是启动页由原来的png改成点9图。

原因:点9图的左侧上侧有拉伸,而右侧下侧是限制显示区域却没有设置,只设置了左侧和上侧。

解决:右侧、下侧都弄上“黑边”

问题:点9图,出现“Error:Execution failed for task ‘:app:mergeDebugResources’”

排查:

  1. 检查图片是否是通过工具转成点9图的(不是手动改的后缀)
  2. 检查图片后缀名称是否是“.9.png.png”等两个后缀名,要保留一个
  3. 左、上可以分段黑边,右、下只能一条黑边

ImageView相关

问题:ImageView滑动屏幕后显示异常(部分iv变大)

现象:LinearLayout内有一排iv,iv的scaleType为centerCrop,LinearLayout为RecyclerView内一个item,滑动Rc后iv显示内容变大了

排查过程:用Layout Inspector查看变形的iv的drawing–isOpaque()为false。第一个iv未变形,其isOpaque()为true

解决:尝试修改iv的android:setScaleType=“fitXY”,问题解决了

解决后再次用Layout Inspector查看,所有iv的drawing–isOpaque()为true

ImageView用wrap_content后还有空白边距

解决:加上属性 android:adjustViewBounds=“true”

TextView相关

tv.setTextColor(color)显示出来的颜色值不对

解决:tv.setTextColor(R.color.red);//不报错,但是显示效果不是红色

改成 tv.setTextColor(getResource().getColor(R.color.red));//即可

显示不出省略号…的问题

<TextView
          android:id="@+id/tv_storeName"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:ellipsize="end"
          android:lines="1"
          android:maxLength="10"
          android:textColor="@color/dark_text_color"
          android:textSize="14sp"
          tools:text="门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2门店2" />

解决:网上查tv有android:inputType="text"属性要去掉。此处的问题是android:maxLength="10"属性,改成android:maxEms="10"即可

TextView跑马灯无效

<TextView
          style="@style/fillX"
          android:text="内容需要超过tv的宽度才能有跑马灯的效果"
          android:ellipsize="marquee"
          android:focusableInTouchMode="true"
          android:focusable="true"
          android:marqueeRepeatLimit="marquee_forever"/>

android:marqueeRepeatLimit="marquee_forever"重复次数

默认是需要获取到焦点才能滚动,所以多个tv一起跑马灯需要自定义TV,重写isFocused()方法返回true

android:ellipsize=”start”—–省略号显示在开头 “…pedia”
android:ellipsize=”end”——省略号显示在结尾 “encyc…”
android:ellipsize=”middle”—-省略号显示在中间 “en…dia”
android:ellipsize=”marquee”–以横向滚动方式显示(需获得当前焦点时)

TextView有值但是width或height为0,导致不显示

(又一城)发生在SBC2.0的燕谷坊的首页的店名,发现用as第一次运行出现这个问题“(debug模式取到值)TextView有值但是width或height为0,导致不显示”。而通过as的Tools-Layout Inspector却得到此TextView的值为“”且width和height为0

尝试解决:textview调用invalidate()、requestLayout()、measure(500,50)、post(Runnable)都不行

后来借鉴stackoverflow相似的问题在xml中在对应TextView中加上

android:animateLayoutChanges="false"

运行了几次是可以的,但是具体是否解决了这个问题还需待验证。

android:animateLayoutChanges=“false” 的作用是关闭系统带的动画效果

布局动画实之LayoutTransition

前面我们说过ViewGroup在设置android:animateLayoutChanges="true"后在添加或者删除子view时可以启用系统带着的动画效果,但这种效果无法通过自定义动画去替换。不过还好android官方为我们提供了LayoutTransition类,通过LayoutTransition就可以很容易为ViewGroup在添加或者删除子view设置自定义动画的过渡效果了。

EditText

edittext和Scrollview滑动条冲突问题

//解决edittext和Scrollview滑动条冲突问题
edt.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        v.getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_UP:
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            default:
                break;
        }
        return false;
    }
});

EditText不自动换行

添加属性android:inputType="textMultiLine"

EditText添加addTextChangedListener后手写时无法正常使用

WebView

WebView不支持拨打电话

// Web视图
private class webViewClient extends WebViewClient {
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (ShowWebActivity.this == null) {
            return false;
        }
        //调用拨号程序
        if (url.startsWith("mailto:") || url.startsWith("geo:") || url.startsWith("tel:") || url.startsWith("smsto:")) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(intent);
            return true;
        }
        return false;
    }
}

调用

// 设置Web视图
webview.setWebViewClient(new webViewClient());

让WebView各Android版本正常播放

AndroidManifest.xml中对应Activity添加硬件加速

<activity ... android:hardwareAccelerated="true" >

这是针对单个Activity起作用。若要整个App起作用,那么要在application标签内添加。

也可在代码中动态添加:

getWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
//一些View不需要硬件加速,则加上
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

WebView无法全屏播放

在onShowCustomView方法中,将获取到的view放到当前Activity的最上方,在onHideCustomView中,将之前的view隐藏或者删除,将原来被覆盖的webview放回来。

public class WebVideoActivity extends Activity {

    private WebView webView;

    /** 视频全屏参数 */
    protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    private View customView;
    private FrameLayout fullscreenContainer;
    private WebChromeClient.CustomViewCallback customViewCallback;

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_xx);
        webView = (WebView) findViewById(R.id.xx);
        initWebView();
    }

    @Override
    protected void onStop() {
        super.onStop();
        webView.reload();
    }

    /** 展示网页界面 **/  public void initWebView() {
        WebChromeClient wvcc = new WebChromeClient();
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setUseWideViewPort(true); // 关键点
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // 不加载缓存内容

        webView.setWebChromeClient(wvcc);
        WebViewClient wvc = new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                webView.loadUrl(url);
                return true;
            }
        };
        webView.setWebViewClient(wvc);

        webView.setWebChromeClient(new WebChromeClient() {

            /*** 视频播放相关的方法 **/

            @Override
            public View getVideoLoadingProgressView() {
                FrameLayout frameLayout = new FrameLayout(WebVideoActivity.this);
                frameLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
                return frameLayout;
            }

            @Override
            public void onShowCustomView(View view, CustomViewCallback callback) {
                showCustomView(view, callback);
            }

            @Override
            public void onHideCustomView() {
                hideCustomView();
            }
        });

        // 加载Web地址
        webView.loadUrl(webUrl);
    }

    /** 视频播放全屏 **/
    private void showCustomView(View view, CustomViewCallback callback) {
        // if a view already exists then immediately terminate the new one
        if (customView != null) {
            callback.onCustomViewHidden();
            return;
        }

        WebVideoActivity.this.getWindow().getDecorView();

        FrameLayout decor = (FrameLayout) getWindow().getDecorView();
        fullscreenContainer = new FullscreenHolder(WebVideoActivity.this);
        fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
        decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
        customView = view;
        setStatusBarVisibility(false);
        customViewCallback = callback;
    }

    /** 隐藏视频全屏 */
    private void hideCustomView() {
        if (customView == null) {
            return;
        }

        setStatusBarVisibility(true);
        FrameLayout decor = (FrameLayout) getWindow().getDecorView();
        decor.removeView(fullscreenContainer);
        fullscreenContainer = null;
        customView = null;
        customViewCallback.onCustomViewHidden();
        webView.setVisibility(View.VISIBLE);
    }

    /** 全屏容器界面 */
    static class FullscreenHolder extends FrameLayout {

        public FullscreenHolder(Context ctx) {
            super(ctx);
            setBackgroundColor(ctx.getResources().getColor(android.R.color.black));
        }

        @Override
        public boolean onTouchEvent(MotionEvent evt) {
            return true;
        }
    }

    private void setStatusBarVisibility(boolean visible) {
        int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
        getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                /** 回退键 事件处理 优先级:视频播放全屏-网页回退-关闭页面 */
                if (customView != null) {
                    hideCustomView();
                } else if (webView.canGoBack()) {
                    webView.goBack();
                } else {
                    finish();
                }
                return true;
            default:
                return super.onKeyUp(keyCode, event);
        }
    }
}

WebView明文存储密码带来的安全漏洞

WebView组件默认开启了密码保存功能,会提示用户是否保存密码,当用户选择保存在WebView中输入的用户名和密码,则会被明文保存到应用数据目录的databases/webview.db中。攻击者可能通过root的方式访问该应用的WebView数据库,从而窃取本地明文存储的用户名和密码。

解决方案

开发者调用 WebView.getSettings().setSavePassword(false),显示调用API设置为false,让WebView不存储密码。

WebView远程代码执行漏洞

Android API level 17以及之前的系统版本,由于程序没有正确限制使用addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法。通过addJavascriptInterface给WebView加入一个 JavaScript桥接接口,JavaScript通过调用这个接口可以直接与本地的Java接口进行交互。就有可能出现手机被安装木马程序、发送扣费短信、通信录或者短信被窃取,甚至手机被远程控制等安全问题。

解决方案

如果一定要使用addJavascriptInterface接口,需使用以下方法:

  1. 设置minSdkVersion值大于或等于17,使应用不能在4.2以下系统上运行
  2. 允许被JavaScript调用的方法必须以@JavascriptInterface进行注解声明

未移除有风险的WebView系统隐藏接口漏洞

根据CVE披露的WebView远程代码执行漏洞信息(CVE-2012-663、CVE-2014-7224),Android系统中存在一共三个有远程代码执行漏洞的隐藏接口。分别是位于android/webkit/webview中的“searchBoxJavaBridge”接口、android/webkit/AccessibilityInjector.java中的“accessibility”接口和“accessibilityTraversal”接口。调用此三个接口的APP在开启辅助功能选项中第三方服务的Android系统上将面临远程代码执行漏洞。

解决方案

如果应用内使用了WebView组件,那么使用 WebView.removeJavascriptInterface(String name) API时,显示的移除searchBoxJavaBridge、accessibility、accessibilityTraversal这三个接口。

减少webview引起的内存泄漏

直接 new WebView 并传入 application context 代替在 XML 里面声明以防止 activity 引用被滥用,能解决90+%的 WebView 内存泄漏。

vWeb =  new WebView(getContext().getApplicationContext());
container.addView(vWeb);

销毁 WebView

if (vWeb != null) {
    vWeb.setWebViewClient(null);
    vWeb.setWebChromeClient(null);
    vWeb.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
    vWeb.clearHistory();

    ((ViewGroup) vWeb.getParent()).removeView(vWeb);
    vWeb.destroy();
    vWeb = null;
} 

Popupwindow

Unable to add window

问题:Unable to add window – token null is not valid; is your activity running?

解决:不能在onCreate()中直接调用显示popupwindow

更改成在触发点击事件后才显示

popupwindow的showAsDropDown失效

解决popupwindow的showAsDropDown失效问题

if (Build.VERSION.SDK_INT >= 24){
    Rect visibleFrame = new Rect();
    view.getGlobalVisibleRect(visibleFrame);
    int height = view.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom;
    baseExpandPopWindow.setHeight(height);
    baseExpandPopWindow.showAsDropDown(view,0,0);
}else {
    baseExpandPopWindow.showAsDropDown(view,0,0);
}

tablayout

tablayout点击显示灰色背景

解决:添加了app:tabBackground="@color/white"

Toast

Toast运行在子线程问题,handler问题:Can't toast on a thread that has not called Looper.prepare()

正确写法:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        ToastUtils.showRoundRectToast("潇湘剑雨-杨充");
        Looper.loop();
    }
}).start();

b.getParent()).removeView(vWeb);
vWeb.destroy();
vWeb = null;
}








## Popupwindow

### Unable to add window

问题:Unable to add window -- token null is not valid; is your activity running?

解决:不能在onCreate()中直接调用显示popupwindow

更改成在触发点击事件后才显示

### popupwindow的showAsDropDown失效

解决popupwindow的showAsDropDown失效问题

```java
if (Build.VERSION.SDK_INT >= 24){
    Rect visibleFrame = new Rect();
    view.getGlobalVisibleRect(visibleFrame);
    int height = view.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom;
    baseExpandPopWindow.setHeight(height);
    baseExpandPopWindow.showAsDropDown(view,0,0);
}else {
    baseExpandPopWindow.showAsDropDown(view,0,0);
}

tablayout

tablayout点击显示灰色背景

解决:添加了app:tabBackground="@color/white"

Toast

Toast运行在子线程问题,handler问题:Can't toast on a thread that has not called Looper.prepare()

正确写法:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        ToastUtils.showRoundRectToast("潇湘剑雨-杨充");
        Looper.loop();
    }
}).start();
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值