Google 发布的Material Design支持库,支持库应该直接用V4提升到V7了,引入了RecycleView。RecylcerView从去年5.0开始发布好一阵子了,这货目前能兼容到API 7,直接继承自Viewgroup,比ListView更为轻量,使用得当的话,完全可以替代ListView/GridView。本文将基于RecyclerView实现ListView/GridView的一些基础特性,和下拉刷新,加载更多,PinnedHeader等一些高级特性。
以前也用eclipse写过recyclerView这样的东西,但是现在用android stuido写的话更加方便,再也不要去拷贝jar去libs里面,也不会出现方法找不到的问题,木前呢我的用到了api23,然后我们开始写咯。
在Android Studio新建一个项目,修改App Module的build.gradle文件,把compileSdkVersion和targetSdkVersion改为23,因为Material Design支持库需要Android 5.0以上作为编译SDK。
同时要加入相关依赖包design和RecyclerView支持包。
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { applicationId "com.example.zengyu.recycleview" minSdkVersion 21 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:23.0.1' compile 'com.android.support:recyclerview-v7:23.0.1' compile 'com.android.support:design:23.0.1' }
一个RecyclerView的Item加载是有顺序的,类似于Activity的生命周期(姑且这么叫),具体可以对adapter的每个方法进行重写打下日志进行查看,具体大致为:
getItemViewType(获取显示类型,返回值可在onCreateViewHolder中拿到,以决定加载哪种ViewHolder)
onCreateViewHolder(加载ViewHolder的布局)
onViewAttachedToWindow(当Item进入这个页面的时候调用)
onBindViewHolder(将数据绑定到布局上,以及一些逻辑的控制就写这啦)
onViewDetachedFromWindow(当Item离开这个页面的时候调用)
onViewRecycled(当Item被回收的时候调用)
RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。
看下主布局就是这么简单:activity_main.xml
<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" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none" /> </RelativeLayout>然后要想实现列表效果也是很简单,分几步走就好了
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
myDataset = new String[]{"JAVA", "Objective-C", "C", "C++", "Swift",
"GO", "JavaScript", "Python", "Ruby", "HTML", "SQL"};
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
1.跟ListView 一样 需要一个 Adapter
2.跟ListView 一样 需要一个 ViewHolder
3.有点不同了, 需要一个LayoutManager
然后可以自定义分割线和动画效果,而且不再有条目事件必须自己去写onclick里面的方法。
目前SDK中提供了三种自带的LayoutManager:
LinearLayoutManager
GridLayoutManager
StaggeredGridLayoutManager
SlideInOutLeftItemAnimator : which applies a slide in/out from/to the left animation SlideInOutRightItemAnimator : which applies a slide in/out from/to the right animation SlideInOutTopItemAnimator : which applies a slide in/out from/to the top animation SlideInOutBottomItemAnimator : which applies a slide in/out from/to the bottom animation ScaleInOutItemAnimator : which applies a scale animation SlideScaleInOutRightItemAnimator : which applies a scale animation with a slide in/out from/to the right animation
package com.example.zengyu.recycleview.widget; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.ColorRes; import android.support.annotation.DimenRes; import android.support.annotation.DrawableRes; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.View; public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration { private static final int DEFAULT_SIZE = 2; private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; protected enum DividerType { DRAWABLE, PAINT, COLOR } protected DividerType mDividerType = DividerType.DRAWABLE; protected VisibilityProvider mVisibilityProvider; protected PaintProvider mPaintProvider; protected ColorProvider mColorProvider; protected DrawableProvider mDrawableProvider; protected SizeProvider mSizeProvider; protected boolean mShowLastDivider; private Paint mPaint; protected FlexibleDividerDecoration(Builder builder) { if (builder.mPaintProvider != null) { mDividerType = DividerType.PAINT; mPaintProvider = builder.mPaintProvider; } else if (builder.mColorProvider != null) { mDividerType = DividerType.COLOR; mColorProvider = builder.mColorProvider; mPaint = new Paint(); setSizeProvider(builder); } else { mDividerType = DividerType.DRAWABLE; if (builder.mDrawableProvider == null) { TypedArray a = builder.mContext.obtainStyledAttributes(ATTRS); final Drawable divider = a.getDrawable(0); a.recycle(); mDrawableProvider = new DrawableProvider() { @Override public Drawable drawableProvider(int position, RecyclerView parent) { return divider; } }; } else { mDrawableProvider = builder.mDrawableProvider; } mSizeProvider = builder.mSizeProvider; } mVisibilityProvider = builder.mVisibilityProvider; mShowLastDivider = builder.mShowLastDivider; } private void setSizeProvider(Builder builder) { mSizeProvider = builder.mSizeProvider; if (mSizeProvider == null) { mSizeProvider = new SizeProvider() { @Override public int dividerSize(int position, RecyclerView parent) { return DEFAULT_SIZE; } }; } } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { int lastChildPosition = -1; int childCount = mShowLastDivider ? parent.getChildCount() : parent.getChildCount() - 1; for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); int childPosition = parent.getChildAdapterPosition(child); if (childPosition < lastChildPosition) { // Avoid remaining divider when animation starts continue; } lastChildPosition = childPosition; if (ViewCompat.getAlpha(child) < 1) { // Avoid remaining divider when animation starts continue; } if (mVisibilityProvider.shouldHideDivider(childPosition, parent)) { continue; } Rect bounds = getDividerBound(childPosition, parent, child); switch (mDividerType) { case DRAWABLE: Drawable drawable = mDrawableProvider.drawableProvider(childPosition, parent); drawable.setBounds(bounds); drawable.draw(c); break; case PAINT: mPaint = mPaintProvider.dividerPaint(childPosition, parent); c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint); break; case COLOR: mPaint.setColor(mColorProvider.dividerColor(childPosition, parent)); mPaint.setStrokeWidth(mSizeProvider.dividerSize(childPosition, parent)); c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint); break; } } } @Override public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(v); setItemOffsets(rect, position, parent); } protected abstract Rect getDividerBound(int position, RecyclerView parent, View child); protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent); /** * Interface for controlling divider visibility */ public interface VisibilityProvider { /** * Returns true if divider should be hidden. * * @param position Divider position * @param parent RecyclerView * @return True if the divider at position should be hidden */ boolean shouldHideDivider(int position, RecyclerView parent); } /** * Interface for controlling paint instance for divider drawing */ public interface PaintProvider { /** * Returns {@link Paint} for divider * * @param position Divider position * @param parent RecyclerView * @return Paint instance */ Paint dividerPaint(int position, RecyclerView parent); } /** * Interface for controlling divider color */ public interface ColorProvider { /** * Returns {@link android.graphics.Color} value of divider * * @param position Divider position * @param parent RecyclerView * @return Color value */ int dividerColor(int position, RecyclerView parent); } /** * Interface for controlling drawable object for divider drawing */ public interface DrawableProvider { /** * Returns drawable instance for divider * * @param position Divider position * @param parent RecyclerView * @return Drawable instance */ Drawable drawableProvider(int position, RecyclerView parent); } /** * Interface for controlling divider size */ public interface SizeProvider { /** * Returns size value of divider. * Height for horizontal divider, width for vertical divider * * @param position Divider position * @param parent RecyclerView * @return Size of divider */ int dividerSize(int position, RecyclerView parent); } public static class Builder<T extends Builder> { private Context mContext; protected Resources mResources; private PaintProvider mPaintProvider; private ColorProvider mColorProvider; private DrawableProvider mDrawableProvider; private SizeProvider mSizeProvider; private VisibilityProvider mVisibilityProvider = new VisibilityProvider() { @Override public boolean shouldHideDivider(int position, RecyclerView parent) { return false; } }; private boolean mShowLastDivider = false; public Builder(Context context) { mContext = context; mResources = context.getResources(); } public T paint(final Paint paint) { return paintProvider(new PaintProvider() { @Override public Paint dividerPaint(int position, RecyclerView parent) { return paint; } }); } public T paintProvider(PaintProvider provider) { mPaintProvider = provider; return (T) this; } public T color(final int color) { return colorProvider(new ColorProvider() { @Override public int dividerColor(int position, RecyclerView parent) { return color; } }); } public T colorResId(@ColorRes int colorId) { return color(mResources.getColor(colorId)); } public T colorProvider(ColorProvider provider) { mColorProvider = provider; return (T) this; } public T drawable(@DrawableRes int id) { return drawable(mResources.getDrawable(id)); } public T drawable(final Drawable drawable) { return drawableProvider(new DrawableProvider() { @Override public Drawable drawableProvider(int position, RecyclerView parent) { return drawable; } }); } public T drawableProvider(DrawableProvider provider) { mDrawableProvider = provider; return (T) this; } public T size(final int size) { return sizeProvider(new SizeProvider() { @Override public int dividerSize(int position, RecyclerView parent) { return size; } }); } public T sizeResId(@DimenRes int sizeId) { return size(mResources.getDimensionPixelSize(sizeId)); } public T sizeProvider(SizeProvider provider) { mSizeProvider = provider; return (T) this; } public T visibilityProvider(VisibilityProvider provider) { mVisibilityProvider = provider; return (T) this; } public T showLastDivider() { mShowLastDivider = true; return (T) this; } protected void checkBuilderParams() { if (mPaintProvider != null) { if (mColorProvider != null) { throw new IllegalArgumentException( "Use setColor method of Paint class to specify line color. Do not provider ColorProvider if you set PaintProvider."); } if (mSizeProvider != null) { throw new IllegalArgumentException( "Use setStrokeWidth method of Paint class to specify line size. Do not provider SizeProvider if you set PaintProvider."); } } } } }
package com.example.zengyu.recycleview.widget; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.DimenRes; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.View; public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration { private MarginProvider mMarginProvider; protected HorizontalDividerItemDecoration(Builder builder) { super(builder); mMarginProvider = builder.mMarginProvider; } @Override protected Rect getDividerBound(int position, RecyclerView parent, View child) { Rect bounds = new Rect(0, 0, 0, 0); int transitionX = (int) ViewCompat.getTranslationX(child); int transitionY = (int) ViewCompat.getTranslationY(child); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); bounds.left = parent.getPaddingLeft() + mMarginProvider.dividerLeftMargin(position, parent) + transitionX; bounds.right = parent.getWidth() - parent.getPaddingRight() - mMarginProvider.dividerRightMargin(position, parent) + transitionX; int dividerSize = getDividerSize(position, parent); if (mDividerType == DividerType.DRAWABLE) { bounds.top = child.getBottom() + params.topMargin + transitionY; bounds.bottom = bounds.top + dividerSize; } else { bounds.top = child.getBottom() + params.topMargin + dividerSize / 2 + transitionY; bounds.bottom = bounds.top; } return bounds; } @Override protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) { outRect.set(0, 0, 0, getDividerSize(position, parent)); } private int getDividerSize(int position, RecyclerView parent) { if (mPaintProvider != null) { return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth(); } else if (mSizeProvider != null) { return mSizeProvider.dividerSize(position, parent); } else if (mDrawableProvider != null) { Drawable drawable = mDrawableProvider.drawableProvider(position, parent); return drawable.getIntrinsicHeight(); } throw new RuntimeException("failed to get size"); } /** * Interface for controlling divider margin */ public interface MarginProvider { /** * Returns left margin of divider. * * @param position Divider position * @param parent RecyclerView * @return left margin */ int dividerLeftMargin(int position, RecyclerView parent); /** * Returns right margin of divider. * * @param position Divider position * @param parent RecyclerView * @return right margin */ int dividerRightMargin(int position, RecyclerView parent); } public static class Builder extends FlexibleDividerDecoration.Builder<Builder> { private MarginProvider mMarginProvider = new MarginProvider() { @Override public int dividerLeftMargin(int position, RecyclerView parent) { return 0; } @Override public int dividerRightMargin(int position, RecyclerView parent) { return 0; } }; public Builder(Context context) { super(context); } public Builder margin(final int leftMargin, final int rightMargin) { return marginProvider(new MarginProvider() { @Override public int dividerLeftMargin(int position, RecyclerView parent) { return leftMargin; } @Override public int dividerRightMargin(int position, RecyclerView parent) { return rightMargin; } }); } public Builder margin(int horizontalMargin) { return margin(horizontalMargin, horizontalMargin); } public Builder marginResId(@DimenRes int leftMarginId, @DimenRes int rightMarginId) { return margin(mResources.getDimensionPixelSize(leftMarginId), mResources.getDimensionPixelSize(rightMarginId)); } public Builder marginResId(@DimenRes int horizontalMarginId) { return marginResId(horizontalMarginId, horizontalMarginId); } public Builder marginProvider(MarginProvider provider) { mMarginProvider = provider; return this; } public HorizontalDividerItemDecoration build() { checkBuilderParams(); return new HorizontalDividerItemDecoration(this); } } }在代码里可以这样去使用:设置颜色高度和左右边距
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(this). color(Color.RED).size(getResources().getDimensionPixelSize(R.dimen.divider)) .margin(getResources().getDimensionPixelSize(R.dimen.leftmargin), getResources().getDimensionPixelSize(R.dimen.rightmargin)).build());其实呢最简单的分割线可以这样写:
public class DividerItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private Drawable mDivider; private int mOrientation; public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent) { Log.v("recyclerview - itemdecoration", "onDraw()"); if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public void drawVertical(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext()); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawHorizontal(Canvas c, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getHeight() - parent.getPaddingBottom(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }该实现类可以看到通过读取系统主题中的
android.R.attr.listDivider
作为Item间的分割线,并且支持横向和纵向。使用可以这样:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST)); 该属性我们可以直接声明在: <!-- Application theme. --> <pre style="background-color:#272822;color:#cfbfad;font-family:'Consolas';font-size:12.0pt;"> <span style="color:#ff007f;"><</span>style name<span style="color:#ff007f;">=</span><span style="color:#ece47e;">"AppTheme" </span>parent<span style="color:#ff007f;">=</span><span style="color:#ece47e;">"AppBaseTheme"</span><span style="color:#ff007f;">> </span><span style="color:#ff007f;"><</span>item name<span style="color:#ff007f;">=</span><span style="color:#ece47e;">"android:listDivider"</span><span style="color:#ff007f;">></span>@drawable<span style="color:#ff007f;">/</span>divider_bg<span style="color:#ff007f;"></</span>item<span style="color:#ff007f;">> </span><span style="color:#ff007f;"></</span>style<span style="color:#ff007f;">></span>
然后最主要的就是adapter了:
// 用于创建onCreateViewHolder @Override public ViewHolder onCreateViewHolder(ViewGroup parentViewGroup, int i) { View item = LayoutInflater.from(context).inflate( R.layout.item_recyclerview, parentViewGroup, false); return new ViewHolder(item); }这几个方法实现一下就好了,然后要加点击事件什么的,就我们必须拉个接口回调。@Override public void onBindViewHolder(final ViewHolder holder,int position) { // 获取当前item中显示的数据 // 设置要显示的数据 holder.textView.setText(list.get(position));}@Override public int getItemCount() { return list.size(); } // 删除指定的Item public void removeData(int position) { list.remove(position); // 通知RecyclerView控件某个Item已经被删除 notifyItemRemoved(position); } public class ViewHolder extends RecyclerView.ViewHolder { public TextView btn_Delete; public TextView textView; public ViewGroup layout_content; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(com.example.zengyu.recycleview.R.id.text); layout_content = (ViewGroup) itemView.findViewById(com.example.zengyu.recycleview.R.id.layout_content); } }
把这个放在adapter里面,然后事件处理时,使用接口回调好了:private IonSlidingViewClickListener mIDeleteBtnClickListener;public void setmIDeleteBtnClickListener(IonSlidingViewClickListener mIDeleteBtnClickListener) { this.mIDeleteBtnClickListener = mIDeleteBtnClickListener; }
public interface IonSlidingViewClickListener { void onItemClick(View view,int position); void onDeleteBtnCilck(View view,int position);}
holder.btn_Delete.setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(View v) { int n = holder.getLayoutPosition();//获取当前条目的position mIDeleteBtnClickListener.onDeleteBtnCilck(v, n); } });最后实现类里面实现这个接口就好了:
@Override public void onDeleteBtnCilck(View view, int position) { Toast.makeText(MainActivity.this, "delete...."+position, Toast.LENGTH_SHORT).show(); adapter.removeData(position);//回调里面刷新数据 }
好了,最后把所有代码贴出来自己看:
package com.example.zengyu.recycleview.widget; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.HorizontalScrollView; import android.widget.TextView; public class SlidingView extends HorizontalScrollView { private TextView mTextView_Delete; private int mScrollWidth; private IonSlidingListener mIonSlidingListener; private Boolean isOpen = false; private Boolean once = false; public SlidingView(Context context) { this(context, null); } public SlidingView(Context context, AttributeSet attrs) { this(context, attrs,0); } public SlidingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.setOverScrollMode(OVER_SCROLL_NEVER); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if(!once){ mTextView_Delete = (TextView) findViewById(com.example.zengyu.recycleview.R.id.tv_delete); once = true; } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(changed){ this.scrollTo(0,0); //获取水平滚动条可以滑动的范围,即右侧按钮的宽度 mScrollWidth = mTextView_Delete.getWidth()+getPaddingRight(); Log.i("asd", "mScrollWidth:" + mScrollWidth); } } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: mIonSlidingListener.onDownOrMove(this); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: changeScrollx(); return true; default: break; } return super.onTouchEvent(ev); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); mTextView_Delete.setTranslationX(l - mScrollWidth); } /** * 按滚动条被拖动距离判断关闭或打开菜单 */ public void changeScrollx(){ if(getScrollX() >= (mScrollWidth/2)){ this.smoothScrollTo(mScrollWidth, 0); isOpen = true; mIonSlidingListener.onMenuIsOpen(this); }else{ this.smoothScrollTo(0, 0); isOpen = false; } } /** * 打开菜单 */ public void openMenu() { if (isOpen){ return; } this.smoothScrollTo(mScrollWidth, 0); isOpen = true; mIonSlidingListener.onMenuIsOpen(this); } /** * 关闭菜单 */ public void closeMenu() { if (!isOpen){ return; } this.smoothScrollTo(0, 0); isOpen = false; } public void setSlidingListener(IonSlidingListener listener){ mIonSlidingListener = listener; } public interface IonSlidingListener{ void onMenuIsOpen(View view); void onDownOrMove(SlidingView slidingView); } }
唉,写完了,上图
好久不写博客了,感觉写一篇怎么这么难啊,唉继续学习吧
代码下载》》》http://download.csdn.net/detail/u013278099/9256553