UI相关问题
RecyclerView相关
recyclerView没有数据问题
RecyclerView.setLayoutManager 不设置这个 就是不行的 就是没有数据的
RecyclerView.getChildAt(position)
空指针
问题:程序中,屏幕可以获取到 6 个子 view,当 getChildAt(position)的position 为 6 或 7 的时候,会有空指针异常
原因:getChildAt()
只能获取到屏幕上显示的部分
解决:View childView = recyclerView.getLayoutManager().findViewByPosition(position);
RecyclerView局部刷新,其他控件会闪动
-
怀疑是宽高重新计算导致的,但写死宽高页面还是会闪动
-
怀疑更新到了这个ImageView,然后glide又重新加载了一次图片
在xml中写入
android:src:"@mipmap/ic_launcher"
还会闪动,排除2 -
怀疑刷新到了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来实现。结果还是不行
2、替换Activity、Adapter,只留item布局不一样,发现问题依然存在。所以定位问题是出在item布局上。查看了item布局发现有
替换了com.u1city.androidframe.customView.FilletFrameLayout为RelativeLayout发现正常了。但是圆角没有了,所以自定义圆角RelativeLayout,但是问题又出现了。所以定位到问题是圆角导致的。
最终版本:最外层用RelativeLayout,里面的圆角再通过别的方式来实现。
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)
处理过程:
-
断点得知是这个广告图所在的ImageView的大小 getWidth()和getMeasureWidth()都是431,尝试在此ImageView前面动态设置大小:用了重新设置setLayoutParam,无效
-
比对上一版的apk是正常,尝试从svn上找动到的文件➡️找到了是启动页由原来的png改成点9图。
原因:点9图的左侧上侧有拉伸,而右侧下侧是限制显示区域却没有设置,只设置了左侧和上侧。
解决:右侧、下侧都弄上“黑边”
问题:点9图,出现“Error:Execution failed for task ‘:app:mergeDebugResources’”
排查:
- 检查图片是否是通过工具转成点9图的(不是手动改的后缀)
- 检查图片后缀名称是否是“.9.png.png”等两个后缀名,要保留一个
- 左、上可以分段黑边,右、下只能一条黑边
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” 的作用是关闭系统带的动画效果
前面我们说过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接口,需使用以下方法:
- 设置minSdkVersion值大于或等于17,使应用不能在4.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();