美文网首页App架构设计及总结
酒店信息、客房信息等页面模板设计

酒店信息、客房信息等页面模板设计

作者: 喝拿铁撸铁 | 来源:发表于2020-10-21 15:51 被阅读0次

    1、相关的View、Activity

    Activity: HotelInfoActivity

    页面标题: TextView mTitleTextView

    页面标题左侧icon: ImageView mTitleImageView

    信息列表: RecyclerView, 定焦需求 mInfoListView

    信息资源(图片轮播、视频): MediaInfoView(自定义view) mMediaInfoView

    信息介绍: TextView(多行换行显示) mInfoIntrTextView

    资源序号: MediaIndexView(自定义view) mInfoIndexTextView

    按键说明: TextView mOperationTextView

    2、业务接口

    信息页数据请求: NetReqMgr mNetReqMgr

    列表项和信息资源的对应: HotelInfoManager mHotelInfoMgr

    数据请求回调: HotelInfoDataListener mHotelInfoDataListener

    信息资源播放: HotelMediaInfoPlayer mHotelInfoPlayer

    图片轮播: HotelPicturePlayer mHotelPicPlayer

    视频播放: HotelVideoPlayer mHotelVideoPlayer

    3、数据及业务处理

    网络接口及数据处理          HotelInfoModule mHotelInfoModule

    4、框架设计

    V---相关的View、Activity

    P---业务逻辑设计的接口

    M---数据及业务处理

    5、业务流程:

    a) 初始化背景图、固定UI;

    b) 初始化业务接口及数据回调;

    c) 发起网络请求NetReqMgr,获取到数据存储到HotelInfoModule中管理;

    d) 初始化标题、信息列表、信息资源对应的资源

    修改记录:

    1、添加SliderView的library库,放在根目录下,launcher_v4\launcher_v4\build.gradle的dependencies添加如下:

    compile project(':library')

    2、添加hotelinfo包,以及目录下文件

    M---

    HotelInfoModule.java

    HotelInfoHttpFactory.java

    HotelInfoBean.java

    Data.java

    List_block.java

    List_element.java

    V---

    HotelInfoActivity.java

    HotelRecyclerViewAdapter.java

    HotelInfoIndexView.java

    HotelInfoItemView.java

    HotelInfoVideoView.java

    P---

    HotelInfoDataListener.java

    HotelInfoManager.java

    HotelMediaInfoPlayer.java

    HotelNetRequestMgr.java

    HotelPicturePlayer.java

    HotelVideoPlayer.java

    技术点:

    1、TextView中插入小图片

    利用SpannableString的特性,添加小图片:

    private void setSpannableTextView(TextView textView, int strId, int iconId) {

    final String ch = "_";

    SpannableString spannableString;

    ImageSpan imageSpan;

    Drawable drawable;

    String text = mContext.getString(strId);

    LogUtils.e(TAG, "<setSpannableTextView>, text = " + text);

    int index = text.indexOf(ch);

    LogUtils.e(TAG, "<setSpannableTextView>, index = " + index);

    if (index >= 0) {

    spannableString = new SpannableString(text);

    imageSpan = new ImageSpan(mContext, iconId, ImageSpan.ALIGN_BASELINE);

    spannableString.setSpan(imageSpan, index, index + ch.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    LogUtils.e(TAG, "<setSpannableTextView>, spannableString = " + spannableString);

    textView.setText(spannableString);

    }

    }

    2、RecyclerView增加item背景

    在Adapter中的onBindViewHolder中添加以下逻辑:

    @Override

    public void onBindViewHolder(InfoListViewHolder holder, int position) {

    holder.itemView.setFocusable(true);

    holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {

    @Override

    public void onFocusChange(View view, boolean hasFocus) {

    if (hasFocus) {

    // 落焦背景图和文字颜色

    holder.itemView.setBackgroundResource(R.drawable.hotel_list_item_focus_bg);

    holder.mTextView.setTextColor(mContext.getResources().getColor(R.color.black));

    } else {

    // 非落焦背景图和文字颜色

    holder.itemView.setBackgroundResource(R.drawable.hotel_list_item_unfocus_bg);

    holder.mTextView.setTextColor(mContext.getResources().getColor(R.color.white));

    }

    }

    });

    holder.mTextView.setText(mInfoList.get(position));

    }

    3、RecyclerView去掉item之间的分隔线

    重写Decoration,即以下添加的item decoration,默认是DividerItemDecoration:

    mRecyclerView.addItemDecoration(new MyItemDecoration(mContext, DividerItemDecoration.VERTICAL));

    主要是重写以下函数getItemOffsets,将下面的bottom改为0

    @Override

    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,

      RecyclerView.State state) {

    if (mDivider == null) {

    outRect.set(0, 0, 0, 0);

    return;

    }

    if (mOrientation == VERTICAL) {

    outRect.set(0, 0, 0, 0/*mDivider.getIntrinsicHeight()*/);

    } else {

    outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);

    }

    }

    详细源码如下:

    public class MyItemDecoration extends RecyclerView.ItemDecoration {

        public static final int HORIZONTAL = LinearLayout.HORIZONTAL;

        public static final int VERTICAL = LinearLayout.VERTICAL;

        private static final String TAG = "DividerItem";

        private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };

        private Drawable mDivider;

        /**

        * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.

        */

        private int mOrientation;

        private final Rect mBounds = new Rect();

        /**

        * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a

        * {@link LinearLayoutManager}.

        *

        * @param context Current context, it will be used to access resources.

        * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.

        */

        public MyItemDecoration(Context context, int orientation) {

            final TypedArray a = context.obtainStyledAttributes(ATTRS);

            mDivider = a.getDrawable(0);

            if (mDivider == null) {

                Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "

                        + "DividerItemDecoration. Please set that attribute all call setDrawable()");

            }

            a.recycle();

            setOrientation(orientation);

        }

        /**

        * Sets the orientation for this divider. This should be called if

        * {@link RecyclerView.LayoutManager} changes orientation.

        *

        * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}

        */

        public void setOrientation(int orientation) {

            if (orientation != HORIZONTAL && orientation != VERTICAL) {

                throw new IllegalArgumentException(

                        "Invalid orientation. It should be either HORIZONTAL or VERTICAL");

            }

            mOrientation = orientation;

        }

        /**

        * Sets the {@link Drawable} for this divider.

        *

        * @param drawable Drawable that should be used as a divider.

        */

        public void setDrawable(@NonNull Drawable drawable) {

            if (drawable == null) {

                throw new IllegalArgumentException("Drawable cannot be null.");

            }

            mDivider = drawable;

        }

        @Override

        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

            if (parent.getLayoutManager() == null || mDivider == null) {

                return;

            }

            if (mOrientation == VERTICAL) {

                drawVertical(c, parent);

            } else {

                drawHorizontal(c, parent);

            }

        }

        private void drawVertical(Canvas canvas, RecyclerView parent) {

            canvas.save();

            final int left;

            final int right;

            //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.

            if (parent.getClipToPadding()) {

                left = parent.getPaddingLeft();

                right = parent.getWidth() - parent.getPaddingRight();

                canvas.clipRect(left, parent.getPaddingTop(), right,

                        parent.getHeight() - parent.getPaddingBottom());

            } else {

                left = 0;

                right = parent.getWidth();

            }

            final int childCount = parent.getChildCount();

            for (int i = 0; i < childCount; i++) {

                final View child = parent.getChildAt(i);

                parent.getDecoratedBoundsWithMargins(child, mBounds);

                final int bottom = 0;//mBounds.bottom + Math.round(child.getTranslationY());

                final int top = 0;//bottom - mDivider.getIntrinsicHeight();

                mDivider.setBounds(left, top, right, bottom);

                mDivider.draw(canvas);

            }

            canvas.restore();

        }

        private void drawHorizontal(Canvas canvas, RecyclerView parent) {

            canvas.save();

            final int top;

            final int bottom;

            //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.

            if (parent.getClipToPadding()) {

                top = parent.getPaddingTop();

                bottom = parent.getHeight() - parent.getPaddingBottom();

                canvas.clipRect(parent.getPaddingLeft(), top,

                        parent.getWidth() - parent.getPaddingRight(), bottom);

            } else {

                top = 0;

                bottom = parent.getHeight();

            }

            final int childCount = parent.getChildCount();

            for (int i = 0; i < childCount; i++) {

                final View child = parent.getChildAt(i);

                parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);

                final int right = mBounds.right + Math.round(child.getTranslationX());

                final int left = right - mDivider.getIntrinsicWidth();

                mDivider.setBounds(left, top, right, bottom);

                mDivider.draw(canvas);

            }

            canvas.restore();

        }

        @Override

        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,

                                  RecyclerView.State state) {

            if (mDivider == null) {

                outRect.set(0, 0, 0, 0);

                return;

            }

            if (mOrientation == VERTICAL) {

                outRect.set(0, 0, 0, 0/*mDivider.getIntrinsicHeight()*/);

            } else {

                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);

            }

        }

    }

    4、RecyclerView定焦需求,上下三分之一位置固定焦点

    需要自定义LinearLayoutManager,即RecyclerView设置的

    mRecyclerView.setLayoutManager(layoutManager);

    详细源码见MyLinearLayoutMananger.java, 重写requestChildRectangleOnScreen, mAdjustTop和mAdjustBottom用于控制离顶部和底部多少距离开始滚动列表

    public class MyLinearLayoutMananger extends LinearLayoutManager {

        private static final String TAG = "HotelInfo_" + "LayoutMgr";

        private static final int SINGLE_ITEM_HEIGHT = DisplayUtil.heightOf(72);

        private final int mAdjustTop = SINGLE_ITEM_HEIGHT * 2;

        private final int mAdjustBottom = SINGLE_ITEM_HEIGHT * 2;

        public MyLinearLayoutMananger(Context context) {

            super(context);

        }

        public MyLinearLayoutMananger(Context context, int orientation, boolean reverseLayout) {

            super(context, orientation, reverseLayout);

        }

        @Override

        public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect, boolean immediate, boolean focusedChildVisible) {

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<requestChildRectangleOnScreen>, mAdjustTop = " + mAdjustTop + ", mAdjustBottom = " + mAdjustBottom + ", immediate = " + immediate);

            final int parentTop = getPaddingTop() + mAdjustTop;

            final int parentBottom = getHeight() - getPaddingBottom() - mAdjustBottom;

            final int childTop = child.getTop() + rect.top;

            final int childBottom = childTop + rect.height();

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<requestChildRectangleOnScreen>, rect.top = " + rect.top + ", rect.height = " + rect.height());

            final int offScreenTop = Math.min(0, childTop - parentTop);

            final int offScreenBottom = Math.max(0, childBottom - parentBottom);

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<requestChildRectangleOnScreen>, offScreenTop = " + offScreenTop + ", offScreenBottom = " + offScreenBottom);

            int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;

            if (dy != 0) {

                if (immediate) {

                    parent.scrollBy(0, dy);

                } else {

                    parent.smoothScrollBy(0, dy);

                }

                return true;

            }

            return false;

        }

    }

    5、RecyclerView焦点记忆

    重写focusSearch,源码参考MyRecyclerView.java

    public class MyRecyclerView extends RecyclerView {

        private static final String TAG = "HotelInfo_" + "RecyclerView";

        private boolean mCanFocusOutHorizontal = true;

        private FocusLostListener mFocusLostListener;

        private FocusGainListener mFocusGainListener;

        private int mCurrentFocusPosition = 0;

        public MyRecyclerView(Context context) {

            this(context, null);

        }

        public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {

            this(context, attrs, 0);

        }

        public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {

            super(context, attrs, defStyle);

            setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

            setChildrenDrawingOrderEnabled(true);

            setItemAnimator(null);

            this.setFocusable(true);

        }

        public boolean isCanFocusOutHorizontal() {

            return mCanFocusOutHorizontal;

        }

        public void setCanFocusOutHorizontal(boolean canFocusOutHorizontal) {

            mCanFocusOutHorizontal = canFocusOutHorizontal;

        }

        @Override

        public View focusSearch(int direction) {

            return super.focusSearch(direction);

        }

        @Override

        public View focusSearch(View focused, int direction) {

            LogUtils.i(TAG, "focusSearch " + focused + ",direction= " + direction);

            View view = super.focusSearch(focused, direction);

            if (focused == null) {

                return view;

            }

            if (view != null) {

                View nextFocusItemView = findContainingItemView(view);

                if (nextFocusItemView == null) {

                    if (!mCanFocusOutHorizontal && (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {

                        return focused;

                    }

                    if (mFocusLostListener != null) {

                        mFocusLostListener.onFocusLost(focused, direction);

                    }

                    return view;

                }

            }

            return view;

        }

        public void setFocusLostListener(FocusLostListener focusLostListener) {

            this.mFocusLostListener = focusLostListener;

        }

        public interface FocusLostListener {

            void onFocusLost(View lastFocusChild, int direction);

        }

        public void setGainFocusListener(FocusGainListener focusListener) {

            this.mFocusGainListener = focusListener;

        }

        public interface FocusGainListener {

            void onFocusGain(View child, View focued);

        }

        @Override

        public void requestChildFocus(View child, View focused) {

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<requestChildFocus>, nextchild = " + child + ", focused = " + focused);

            if (!hasFocus()) {

                if (mFocusGainListener != null) {

                    mFocusGainListener.onFocusGain(child, focused);

                }

            }

            super.requestChildFocus(child, focused);

            mCurrentFocusPosition = getChildViewHolder(child).getAdapterPosition();

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<requestChildFocus>, focusPos = " + mCurrentFocusPosition);

        }

        @Override

        public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {

            View view = null;

            if (getLayoutManager() != null) {

                view = getLayoutManager().findViewByPosition(mCurrentFocusPosition);

            }

            if (this.hasFocus() || mCurrentFocusPosition < 0 || view == null) {

                super.addFocusables(views, direction, focusableMode);

            } else if (view.isFocusable()) {

                views.add(view);

            } else {

                super.addFocusables(views, direction, focusableMode);

            }

        }

        /**

        *

        * @param childCount

        * @param i

        * @return

        */

        @Override

        protected int getChildDrawingOrder(int childCount, int i) {

            View focusedChild = getFocusedChild();

            if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<getChildDrawingOrder>, focusedChild = " + focusedChild);

            if (focusedChild == null) {

                return super.getChildDrawingOrder(childCount, i);

            } else {

                int index = indexOfChild(focusedChild);

                if (LogUtils.isOpenLogd()) LogUtils.d(TAG, "<getChildDrawingOrder>, index = " + index + ", i = " + i + ", count = " + childCount);

                if (i == childCount - 1) {

                    return index;

                }

                if (i < index) {

                    return i;

                }

                return i + 1;

            }

        }

    }

    6、RecyclerView焦点丢失问题

    1.adapter的setHasStableIds设置成true

    2.重写adapter的getItemId方法

    @Override

    public long getItemId(int position) {

        return position;

    }

    3.mRecyclerView.setItemAnimator(null);

    拓展:

    RecyclerView,LayoutManager,Adapter,ViewHolder,ItemDecoration之间的关系

    7、RecyclerView item文字超长滚动显示

    在控件布局中配置

    android:focusable="true"

    android:singleLine="true"

    android:ellipsize="marquee"

    android:marqueeRepeatLimit="marquee_forever"

    在adapter的onFocusChange中添加:

    mTextView.setSelected(true);

    8、RecyclerView item动画超出边界

    在父布局的父布局中添加android:clipChildren="false",允许view超出布局边界

    9、RecyclerView的边界动效

    Launcher的AnimUtils已经封装为工具类,只需传入需要做动画的view,例如

    焦点动效:AnimUtils.doFocusedScaleAnim(itemView);

    失焦点动效:AnimUtils.doUnFocusScaleAnim(itemView);

    向上移动动效:AnimUtils.doDirectionAnim(View.FOCUS_UP, focusView);

    向下移动动效:AnimUtils.doDirectionAnim(View.FOCUS_DOWN, focusView);

    10、UE一般要求RecyclerView的item之间留有阴影,要求两个item有重叠部分,所以在设置item的布局时,注意给marginTop设置负值

    mHotelInfoUtils.setFrameLayoutParams(itemView,

    HotelInfoUtils.HOTEL_INFO_LIST_ITEM_WIDTH,

    HotelInfoUtils.HOTEL_INFO_LIST_ITEM_HEIGHT,

    0,

    -1 * HotelInfoUtils.HOTEL_INFO_RECYCLER_MARGIN_TOP);

    public FrameLayout.LayoutParams setFrameLayoutParams(View view, int width, int height, int marginStart, int marginTop, int marginRight, int marginBottom) {

    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);

    params.setMargins(marginStart, marginTop, marginRight, marginBottom);

    view.setLayoutParams(params);

    return params;

    }

    11、RecyclerView item显示选中icon

    TextView调用setCompoundDrawables可以设置,利用drawable的setBounds设置icon宽高位置,利用setCompoundDrawablePadding设置文字和icon的间距:

    Drawable drawable = mContext.getResources().getDrawable(R.drawable.hotel_item_sel);

    LogUtils.e(TAG, "<setSelectItemIconVisible>, drawable = " + drawable);

    if (drawable != null) {

    drawable.setBounds(0, 0, DisplayUtil.widthOf(4), DisplayUtil.heightOf(28));

    textView.setCompoundDrawablePadding(DisplayUtil.heightOf(18));

    textView.setCompoundDrawables(drawable, null, null, null);

    }

    碰到一个问题,icon显示不出来,网上说要先调用setBounds,这里和TextView的宽度设置有关,但是由于设置TextView宽度用于末尾显示省略号,最终调用

    TextView.setMaxWidth设置文字宽度,而不是设置TextView的布局宽度解决:

    if (mTextView != null) {

    mTextView.setTextColor(mContext.getResources().getColor(R.color.white));

    mTextView.setSingleLine();

    mTextView.setFocusable(true);

    // 7 words and 1 ellipsis, about 30ps one word

    mTextView.setMaxWidth(HotelInfoUtils.HOTEL_INFO_LIST_TEXT_WIDTH);

    mTextView.setEllipsize(TextUtils.TruncateAt.END);

    mTextView.setMarqueeRepeatLimit(0);

    mTextView.setSelected(false);

    }

    另外,item高亮图片对显示个数有影响,因为用的xx.9.png图片,只有中间拉伸部分是可以显示文字的,这个长度直接决定了能显示多少文字

    相关文章

      网友评论

        本文标题:酒店信息、客房信息等页面模板设计

        本文链接:https://www.haomeiwen.com/subject/szhemktx.html