当前位置:网站首页>A blog allows you to understand the use of material design

A blog allows you to understand the use of material design

2022-06-23 01:13:00 Lao Ma's programming journey

Material Design Basic concepts of

Material Design yes Google Design of a set of visual language , Combine the priority classical design principles with scientific and technological innovation , Provide a set of visual and interactive design specifications for developers . Mobile device is the basic object of this design language , Let users work on different platforms 、 Consistent experience on devices of different sizes . Material Design Emphasize immediate feedback on interactions , That is, the user's touch and other behaviors app Need to give an immediate response . meanwhile Material Design The application is required to bring a sense to the user , Let users immerse themselves in the current application when using . for example Google The immersive status bar is given “ Tools ”, Hope to change StatusBar and NavigationBar To give users a stronger sense of integration , Focus on the content provided by the application itself . Google From animation 、 Color 、 style 、 Touch feedback 、 Layout, etc Material Design Design requirements . Whether it is a single control or graphic layout ,Google Have given clear design instructions , Interested students can go to the official link mentioned above to learn more .

RecyclerView Use

Write entry layout :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:orientation="vertical">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

Write Adapter And its internal classes are customized ViewHolder:

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {

    private List<String> mDatas;
    private Context mContext;

    public MyRecyclerViewAdapter(Context context, List<String> datas) {
        mContext = context;
        mDatas = datas;
    }

    // Customize ViewHolder
    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tv_item;

        MyViewHolder(View itemView) {
            super(itemView);
            tv_item = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // establish ViewHolder
        View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        // Data binding 
        holder.tv_item.setText(mDatas.get(position));
        // Set Click to listen 
        holder.tv_item.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, mDatas.get(position), Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        // Dataset size 
        return mDatas.size();
    }

}

stay Activity The use of , By setting up different LayoutManager You can achieve different layout effects :

public class MDRecyclerViewActivity extends AppCompatActivity {

    private RecyclerView rv_list;
    private MyRecyclerViewAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_md_recyclerview);

        rv_list = (RecyclerView) findViewById(R.id.rv_list);

        List<String> datas = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            datas.add(" The first " + i + " Data ");
        }

        mAdapter = new MyRecyclerViewAdapter(this, datas);
        // Vertical linearity , Do not reverse the layout 
//        rv_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        // Table layout 
//        rv_list.setLayoutManager(new GridLayoutManager(this, 3));
        // Waterfall flow layout 
        rv_list.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
        rv_list.setAdapter(mAdapter);

    }
}

Inflate What to pay attention to when :

stay Adapter Medium onCreateViewHolder, need Inflate Layout file , There are three ways of writing :

View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
View itemView = View.inflate(parent.getContext(), R.layout.item_list, parent);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);

Generally speaking, there is no problem with the writing , But when we were onBindViewHolder In the layout TextView Of LayoutParams When , It is possible to return null . Writing 2 is direct Crash, because ItemView The layout already has a Parent 了 (Inflate When the time is right ItemView Add to Recycleview 了 ), You cannot add another Parent(Recycleview Add again ItemView). Three is one 、 Two compatible schemes of II , Recommend this style of writing .

Add / delete interface stay Adapter Interfaces added and deleted in :

// Addition and deletion of entries 
public void addItem(String data, int position) {
    mDatas.add(position, data);
    notifyItemInserted(position);
}

public void removeItem(int position) {
    mDatas.remove(position);
    notifyItemRemoved(position);
}

Note if you want to use RecyclerView Add / delete animation provided , Then you need to use the new notify Method .

Add an entry and click listen Customize a click callback interface :

// Click on the entry 
ItemClickListener mItemClickListener;

public interface ItemClickListener {
    void onclick(int position, String data);
}

public void setItemClickListener(ItemClickListener listener) {
    mItemClickListener = listener;
}

public abstract class ItemClickListenerPosition implements View.OnClickListener {

    private int mPosition;

    public ItemClickListenerPosition(int position) {
        mPosition = position;
    }

    public int getPosition() {
        return mPosition;
    }
}

ItemClickListenerPosition Is a custom OnClickListener, The purpose is to make Position And listening , It also uses getLayoutPosition Method . Prevents clicking Position Confused questions .

(onBindViewHolder() Method position It's not real-time , For example, after we delete the element ,item Of position It hasn't changed .)

And then in onBindViewHolder Listen inside :

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {

    // Data binding 

    // Set entry listening 
    holder.itemView.setOnClickListener(new ItemClickListenerPosition(holder.getLayoutPosition()) {
        @Override
        public void onClick(View v) {
            if (mItemClickListener != null) {
                mItemClickListener.onclick(getPosition(), mDatas.get(getPosition()));
            }
        }
    });
}

Want to learn more RecyclerView Use , Please refer to 《 A blog understands Recyclerview Use 》

DrawerLayout+NavigationView

Use DrawerLayout Achieve sideslip : Define a layout :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#D197F2"
        app:title=" I'm the title "
        app:titleTextColor="#fff"/>

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text=" Content "/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="200dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="@android:color/holo_blue_light"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text=" Sideslip menu 1"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text=" Sideslip menu 2"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text=" Sideslip menu 3"/>

        </LinearLayout>

    </android.support.v4.widget.DrawerLayout>


</LinearLayout>

The layout sideslip menu includes the menu section and the content section , use DrawerLayout Come and wrap it up . among , The root layout of the menu section needs to be added android:layout_gravity=”start”, If it is right sliding , Change it to end that will do .

In this way, a basic side sliding effect can be achieved .

DrawerLayout Is actually realized through ViewDragHelper To achieve ,DrawerLayout The relevant code of the constructor is as follows :

public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    mLeftCallback = new ViewDragCallback(Gravity.LEFT);
    mRightCallback = new ViewDragCallback(Gravity.RIGHT);

    mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    mLeftDragger.setMinVelocity(minVel);
    mLeftCallback.setDragger(mLeftDragger);
}

utilize DrawerLayout Some effects can be achieved by monitoring for example , When we can achieve sideslip ,Toolbar The button in the upper left corner changes in real time , We can add a listener ActionBarDrawerToggle:

toolbar = (Toolbar) findViewById(R.id.toolbar);
drawer = (DrawerLayout) findViewById(R.id.drawer);

ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
toggle.syncState();

drawer.addDrawerListener(toggle);

Analyze the implementation principle : among ,ActionBarDrawerToggle Realized DrawerLayout.DrawerListener. And in the process of sliding Refresh the... In the upper left corner Drawerable:

@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
    setPosition(Math.min(1f, Math.max(0, slideOffset)));
}

setPosition The implementation is as follows :

private void setPosition(float position) {
    if (position == 1f) {
        mSlider.setVerticalMirror(true);
    } else if (position == 0f) {
        mSlider.setVerticalMirror(false);
    }
    mSlider.setProgress(position);
}

In fact, it changes constantly in the process of sliding mSlider( A custom Drawerable object ) Of Progress, So as to constantly refresh the status .

therefore , We can do some custom effects , For example, zoom when sliding 、 translation :

drawer.addDrawerListener(new DrawerLayout.DrawerListener() {

    @Override
    public void onDrawerStateChanged(int newState) {
        //  The state changes 

    }

    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
        //  In the process of sliding, it continuously calls back  slideOffset:0~1
        View content = drawer.getChildAt(0);
        float scale = 1 - slideOffset;//1~0
        float leftScale = (float) (1 - 0.3 * scale);
        float rightScale = (float) (0.7f + 0.3 * scale);//0.7~1
        drawerView.setScaleX(leftScale);//1~0.7
        drawerView.setScaleY(leftScale);//1~0.7

        content.setScaleX(rightScale);
        content.setScaleY(rightScale);
        content.setTranslationX(drawerView.getMeasuredWidth() * (1 - scale));//0~width

    }

    @Override
    public void onDrawerOpened(View drawerView) {
        //  open 

    }

    @Override
    public void onDrawerClosed(View drawerView) {
        //  close 

    }
});

DrawerLayout+NavigationView Achieve sideslip

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--  The content part  -->
    <FrameLayout
        android:id="@+id/fl"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />

    <!--  Part of the menu  -->
    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/navigation_headerlayout"
        app:menu="@menu/navigation_menu"
        />

</android.support.v4.widget.DrawerLayout>

We specified the following headers :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_marginTop="20dp"
        android:src="@drawable/icon_people"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text=" Lu baobao "
        android:textSize="20sp"/>

</LinearLayout>

The menu section is as follows (menu Create under folder ), Menus can be nested :

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/action_gallery"
        android:icon="@android:drawable/ic_menu_gallery"
        android:orderInCategory="100"
        android:title=" Photo album "
        />
    <item
        android:id="@+id/action_details"
        android:icon="@android:drawable/ic_menu_info_details"
        android:orderInCategory="100"
        android:title=" details "
        />
    <item
        android:id="@+id/action_about"
        android:icon="@android:drawable/ic_menu_help"
        android:orderInCategory="100"
        android:title=" About "
        />
    <item
        android:id="@+id/action_music"
        android:icon="@android:drawable/ic_menu_more"
        android:orderInCategory="100"
        android:title=" music "
        >
        <menu>
            <item
                android:id="@+id/action_play"
                android:icon="@android:drawable/ic_media_play"
                android:title=" Play "/>
            <item
                android:id="@+id/action_pause"
                android:icon="@android:drawable/ic_media_pause"
                android:title=" Pause "/>
        </menu>
    </item>

</menu>

Up to now , You can achieve side sliding , Finally, we add the corresponding click event , Then close the menu :

nav_view = (NavigationView) findViewById(R.id.nav_view);
drawer = (DrawerLayout) findViewById(R.id.drawer);
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Toast.makeText(NavigationViewActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show();
        drawer.closeDrawer(nav_view);
        return false;
    }
});

nav_view.getHeaderView(0).findViewById(R.id.iv_icon).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(NavigationViewActivity.this, " Click on the icon in the head ", Toast.LENGTH_SHORT).show();
        drawer.closeDrawer(nav_view);
    }
});

SnackBar

// among View It's an anchor 
Snackbar snackbar = Snackbar.make(v, " Whether to open XXX Pattern ", Snackbar.LENGTH_SHORT);

// You can only set one Action
snackbar.setAction(" open ", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.e(TAG, " open XXX Pattern ");
    }
});
// Monitor on and off 
snackbar.setCallback(new Snackbar.Callback() {
    @Override
    public void onShown(Snackbar snackbar) {
        super.onShown(snackbar);
        Log.e(TAG, " Show ");
    }

    @Override
    public void onDismissed(Snackbar snackbar, int event) {
        super.onDismissed(snackbar, event);
        Log.e(TAG, " close ");
    }
});
snackbar.show();

Snackbar Of Duration There are three kinds of :

Snackbar.LENGTH_SHORT Snackbar.LENGTH_LONG Snackbar.LENGTH_INDEFINITE— Infinite length make Method passes in an anchor , Here comes a Button object . Then you can also set actions and callback monitoring .

Snackbar For details on the use of 《 Lightweight controls SnackBar Use and source code analysis 》

TextInputLayout

Layout :

<android.support.design.widget.TextInputLayout
    android:id="@+id/til_input"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:hintAnimationEnabled="true">

    <EditText
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint=" Please enter a user name "/>

</android.support.design.widget.TextInputLayout>

hintAnimationEnabled Property is to set whether it is enabled Hint Animation .

It should be noted that ,TextInputLayout Must contain a EditText.

Here is a basic example :

public class TextInputMainActivity extends AppCompatActivity {

    private TextInputLayout til_input;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_input);

        til_input = (TextInputLayout) findViewById(R.id.til_input);
        til_input.getEditText().addTextChangedListener(new MaxTextTextWatcher(til_input, " The number of words cannot be greater than 6", 6));

        // Opening count 
        til_input.setCounterEnabled(true);
        til_input.setCounterMaxLength(6);

    }

    class MaxTextTextWatcher implements TextWatcher {

        private TextInputLayout mTextInputLayout;
        private String mErrorString;
        private int maxTextCount;

        public MaxTextTextWatcher(TextInputLayout textInputLayout, String errorString, int maxTextCount) {
            mTextInputLayout = textInputLayout;
            mErrorString = errorString;
            this.maxTextCount = maxTextCount;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            String str = mTextInputLayout.getEditText().getText().toString().trim();
            if (!TextUtils.isEmpty(str)) {
                if (str.length() > maxTextCount) {
                    // Display error 
                    // Set the error prompt 
                    mTextInputLayout.setError(mErrorString);
                    mTextInputLayout.setErrorEnabled(true);
                } else {
                    // Closing error 
                    mTextInputLayout.setErrorEnabled(false);
                }
            }
        }
    }
}

In this case , We used TextInputLayout Error prompt for 、 Word count function , The basic use is relatively simple .

stay TextInputLayout Can easily pass through getEditText Method to find what it contains EditText.、 When an error is displayed , You need to set the error prompt first , Set each time you display . Most of the attributes can be passed through xml How to set , The dynamic setting through the code here is just for the convenience of demonstration .

TextInputLayout For details, see Powerful prompt control TextInputLayout Use and source code analysis

Toolbar

<android.support.v7.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    app:logo="@drawable/ic_launcher"
    app:subtitle=" Subtitle "
    app:navigationIcon="@drawable/abc_ic_ab_back_mtrl_am_alpha"
    app:subtitleTextColor="#fff"
    app:title=" I'm the title "
    app:titleTextColor="#fff"></android.support.v7.widget.Toolbar>

Toolbar It's a ViewGroup, Child controls can be placed inside . therefore , If you want to center the title , Then put in one TextView Well .

there ?attr/colorPrimary Is the color value of the system used , Of course, we can also rewrite in the topic .

Be careful :Toolbar Need to use Appcompat A set of things .

Return to listening :

toolbar.setNavigationOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        finish();
    }
});

Realization Toolbar As the interface slides, the transparency changes First we need a layout , By relative arrangement Toolbar Press on ScrollView( perhaps ListView、RecyclerView) On top of .Toolbar The height of ScrollView Both the upper and inner margins use the system's actionBarSize.

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.nan.advancedui.toolbar.MyScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="?attr/actionBarSize">

        <!-- Here is our content layout -->

    </com.nan.advancedui.toolbar.MyScrollView>

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:title=" title "
        >
    </android.support.v7.widget.Toolbar>

</RelativeLayout>

Also pay attention to ScrollView Set two more properties , Otherwise, the upper and inner margins will remain after sliding out :

android:clipToPadding=”false” Whether the drawing range of the control is not in Padding Inside .false: The range will be taken into account when drawing padding It will be indented in . android:clipChildren=”false” Whether the child control can not exceed padding Region ( such as ScrollView When sliding up ,child You can slide out of this area ) Then listen for sliding events , If this is ScrollView Words , You need a custom override method to listen :

public class MyScrollView extends ScrollView {

    private OnAlphaListener listener;


    public void setOnAlphaListener(OnAlphaListener listener) {
        this.listener = listener;
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (listener != null) {
            int scrollY = getScrollY();
            int screen_height = getContext().getResources().getDisplayMetrics().heightPixels;
            if (scrollY <= screen_height / 3f) {//0~1f, Transparency should be 1~0f
                listener.onAlpha(1 - scrollY / (screen_height / 3f));//alpha= The height of the slide /(screen_height/3f)
            }
        }
    }
}

The calculation of transparency needs to be based on the actual situation Customize an interface callback ,Activity(Fragment) real :

public interface OnAlphaListener {

    void onAlpha(float alpha);

}

The logic of the interface is as follows :

public class ToolbarActivity extends AppCompatActivity implements OnAlphaListener {

    private Toolbar mToolbar;
    private MyScrollView mScrollview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toolbar);

        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        mScrollview = (MyScrollView) findViewById(R.id.scrollView);

        mScrollview.setOnAlphaListener(this);
    }

    @Override
    public void onAlpha(float alpha) {
        mToolbar.setAlpha(alpha);
    }
}

SearchView

SearchView It's also V7 Package controls , Generally, it is also similar to Toolbar The menu in .

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.ricky.materialdesign.toolbar.MainActivity"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:orderInCategory="100"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always"
        android:title=" lookup "/>
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        app:showAsAction="never"
        android:title=" Set up "/>
    <item
        android:id="@+id/action_share"
        android:orderInCategory="100"
        app:showAsAction="always"
        android:title=" Share "
        android:icon="@android:drawable/ic_menu_share"/>
    <item
        android:id="@+id/action_edit"
        android:orderInCategory="100"
        app:showAsAction="ifRoom"
        android:title=" edit "
        android:icon="@android:drawable/ic_menu_edit"/>

</menu>

here app:actionViewClass=”android.support.v7.widget.SearchView” The menu is specified View It's a SearchView. So we can use it in our code :

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);

    //SearchView stay Menu Inside , We go through Item Of getActionView You can find it 
    MenuItem item = menu.findItem(R.id.action_search);
    SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
    // As soon as the settings come out, the search box will be displayed directly ---SearchView
    searchView.setIconified(false);

    // The search box is rendered and cannot be hidden 
    //searchView.setIconifiedByDefault(false);

    // Sometimes we need to implement custom extension effects 
    // By conjecture ,searchView A layout is used , Go to appcompat Found inside abc_search_view.xml, The properties of the controls in the 
    ImageView icon = (ImageView) searchView.findViewById(R.id.search_go_btn);
    icon.setImageResource(R.drawable.abc_ic_voice_search_api_mtrl_alpha);
    icon.setVisibility(View.VISIBLE);
    searchView.setMaxWidth(200);

    // Input prompt 
    SearchView.SearchAutoComplete et = (SearchView.SearchAutoComplete) searchView.findViewById(R.id.search_src_text);
    et.setHint(" Enter the trade name or initials ");
    et.setHintTextColor(Color.WHITE);

    // Set whether the submit button is available ( so )
    searchView.setSubmitButtonEnabled(true);

    // Submit button listening 
    icon.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, " Submit ", 1).show();
        }
    });

    // image AutoCompleteTextView Use the same prompts 
    //searchView.setSuggestionsAdapter(adapter);

    // Monitoring focus changes 
    searchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            // TODO Auto-generated method stub

        }
    });

    //searchView Turn off listening 
    searchView.setOnCloseListener(new OnCloseListener() {

        @Override
        public boolean onClose() {
            // TODO Auto-generated method stub
            return false;
        }
    });

    searchView.setOnSearchClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, " Submit ", 0).show();
        }
    });

    // Monitor text changes , Call query 
    searchView.setOnQueryTextListener(new OnQueryTextListener() {

        @Override
        public boolean onQueryTextSubmit(String text) {
            // Submit text 
            Toast.makeText(MainActivity.this, " Submit text :"+text, 0).show();
            return false;
        }

        @Override
        public boolean onQueryTextChange(String text) {
            //  Call back when the text changes 
            System.out.println(" Text changes ~~~~~"+text);

            return false;
        }
    });

    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}

TabLayout

Let's say TabLayout+ViewPager+Fragment For example , about TabLayout Basic use of .

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabIndicatorColor="#4ce91c"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="#4ce91c"
        app:tabTextColor="#ccc"
        app:tabIndicatorHeight="5dp"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />

</LinearLayout>

among , The properties that need attention are :

app:tabIndicatorColor="@color/colorPrimary_pink"// The color of the indicator 
app:tabTextColor="@color/colorPrimary_pink"//tab The color of the words 
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"// Selected tab The color of the words 
app:tabMode="fixed"//scrollable: Sliding ;fixed: Can't slide , divide equally tabLayout Width 
app:tabGravity="center"// fill:tab Fill the entire width evenly ;center:tab centered 

Need to switch Fragment, For convenience , We reuse one Fragment:

public class NewsDetailFragment extends Fragment {

    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView tv = new TextView(getContext());
        Bundle bundle = getArguments();
        String title = bundle.getString("title");
        tv.setBackgroundColor(Color.rgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255)));
        tv.setText(title);
        return tv;
    }

}

Activity Code for :

public class TabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private String[] title = {
            " headlines ",
            " Journalism ",
            " entertainment ",
            " sports ",
            " Technology ",
            " beauty ",
            " Finance and economics, ",
            " automobile ",
            " house ",
            " headlines "
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab_layout);

        final ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
        tabLayout = (TabLayout) findViewById(R.id.tablayout);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());


        //1.TabLayout and Viewpager relation 
//        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
//
//            @Override
//            public void onTabUnselected(TabLayout.Tab arg0) {
//
//            }
//
//            @Override
//            public void onTabSelected(TabLayout.Tab tab) {
//                //  Callback when selected 
//                viewPager.setCurrentItem(tab.getPosition(), true);
//            }
//
//            @Override
//            public void onTabReselected(TabLayout.Tab tab) {
//
//            }
//        });
        //2.ViewPager Sliding associations tabLayout
//        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        // Set up tabLayout The label of comes from PagerAdapter
//        tabLayout.setTabsFromPagerAdapter(adapter);


        // Set up tabLayout The label of comes from PagerAdapter
        tabLayout.setupWithViewPager(viewPager);

        viewPager.setAdapter(adapter);

        // Set up Indicator The left-right spacing (Indicator Width )
        setIndicator(this, tabLayout, 15, 15);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return title[position];
        }

        @Override
        public Fragment getItem(int position) {
            Fragment f = new NewsDetailFragment();
            Bundle bundle = new Bundle();
            bundle.putString("title", title[position]);
            f.setArguments(bundle);
            return f;
        }

        @Override
        public int getCount() {
            return title.length;
        }

    }

    // The following three methods are set Indicator

    public static void setIndicator(Context context, TabLayout tabs, int leftDip, int rightDip) {
        Class<?> tabLayout = tabs.getClass();
        Field tabStrip = null;
        try {
            tabStrip = tabLayout.getDeclaredField("mTabStrip");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        tabStrip.setAccessible(true);
        LinearLayout ll_tab = null;
        try {
            ll_tab = (LinearLayout) tabStrip.get(tabs);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        int left = (int) (getDisplayMetrics(context).density * leftDip);
        int right = (int) (getDisplayMetrics(context).density * rightDip);

        for (int i = 0; i < ll_tab.getChildCount(); i++) {
            View child = ll_tab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = left;
            params.rightMargin = right;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }

    public static DisplayMetrics getDisplayMetrics(Context context) {
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        return metric;
    }

    public static float getPXfromDP(float value, Context context) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
                context.getResources().getDisplayMetrics());
    }
}

New tabLayout.setupWithViewPager(viewPager); Method instead of in the comment 3 There's a way , In fact, the internal work is the same .TabLayout No modification is provided by default Indicator A function of width , We need to set it by reflection .

use TabLayout Realize bottom navigation ( Compared with the traditional TabHost, It is sliding )

It's just three steps : 1. Put... In the layout TabLayout At the bottom of the layout 2. Remove the bottom indicator,app:tabIndicatorHeight=”0dp” 3. Achieve your own effect , Custom label layout The code is as follows :

for (int i = 0; i < tabLayout.getTabCount(); i++) {
    TabLayout.Tab tab = tabLayout.getTabAt(i);
    tab.setCustomView(view);
}

CardView

CardView It's just one. ViewGroup, Sub layouts can be placed inside

<android.support.v7.widget.CardView
    android:layout_width="300dp"
    android:layout_height="200dp"
    android:layout_margin="16dp"
    android:clickable="true"
    android:foreground="?attr/selectableItemBackground"
    android:stateListAnimator="@drawable/z_translation"
    app:cardCornerRadius="10dp"
    app:cardElevation="10dp">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/test"/>

</android.support.v7.widget.CardView>

among ,cardElevation Is to set the height , The higher the height , The more obvious the shadow .foreground Property is to set the click water ripple effect .cardCornerRadius Is to set the size of the fillet .stateListAnimator Is to set the click animation effect , After clicking , Press down ,z_translation as follows :

<selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:state_pressed="true">
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="-15dp"
            android:valueType="floatType"/>
    </item>

    <item>
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="0dp"
            android:valueType="floatType"/>
    </item>

</selector>

CardView Compatibility development establish layout、layout-v21 Two sets of layouts , Write two copies according to the following differences CardView Layout file for . Particular attention is paid to stateListAnimator This attribute , If the smallest SDK Version below 21,AS Will warn you . 1. The nuances of shadows 5.x System : The margin shadow is small , You need to add margins manually 16dp,android:layout_margin=”16dp” 4.x System : The margin shadow is large , Manually modify the margins 0dp( reason : The shadow effect is automatically set in the compatibility package margin To deal with it 16dp) 2. Subtle differences in fillet effect 5.x System : Both the picture and the layout can well present the rounded corner effect , The picture is rounded , therefore 5.x There is no need to set the above app:contentPadding

4.x System : The figure cannot be rounded ( The right angle of the picture will top CardView On the edge ), If you want to make 5.x Same effect : It can be processed into rounded corners when loading pictures ( And CardView The size of the fillet is the same ), therefore 4.x There is no need to set the above app:contentPadding, So as to look better

3. The difference of water ripple effect 5.x System : Can pass android:foreground=”?attr/selectableItemBackground” Realization 4.x System : You have to implement it yourself

4. Click on the difference in animation 5.x System : Can pass android:stateListAnimator=”@drawable/z_translation” Animate 4.x System : You cannot animate the above , because 4.x No, z The concept of axis

FloatingActionButton

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_gravity="right|bottom"
    android:onClick="rotate"
    android:src="@drawable/ic_add_white_24dp"
    app:backgroundTint="?attr/colorPrimary"
    app:elevation="10dp"
    app:fabSize="normal"
    app:rippleColor="#f00"
    />

among : 1.src Property is the setting icon 2.backgroundTint Is to set the background color ( The icon has a transparent background ) 3.elevation Is to set the shadow size 4.fabsize Is to set the size of the icon , It's usually normal( No settings ) 5.rippleColor Is to set the color of the water ripple Click on the event below ( rotate ):

private boolean reverse = false;

public void rotate(View v) {
    float toDegree = reverse ? -180f : 180f;
    ObjectAnimator animator = ObjectAnimator
            .ofFloat(v, "rotation", 0.0f, toDegree)
            .setDuration(400);
    animator.start();
    reverse = !reverse;
}

FloatingActionButton Animation programme 1: When the list slides FloatingActionButton Hide and show , By customizing OnScrollListener Realization

public class FabScrollListener extends OnScrollListener {
    private static final int THRESHOLD = 20;
    private int distance = 0;
    private HideScrollListener hideListener;
    private boolean visible = true;// Whether or not visible 

    public FabScrollListener(HideScrollListener hideScrollListener) {
        this.hideListener = hideScrollListener;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        /**
         * dy:Y Increment in axis direction 
         *  There are positive and negative 
         *  When the animation is being executed , Don't do it anymore 
         */
        if (distance > THRESHOLD && visible) {
            // Hide animation 
            visible = false;
            hideListener.onHide();
            distance = 0;
        } else if (distance < -THRESHOLD && !visible) {
            // Show animation 
            visible = true;
            hideListener.onShow();
            distance = 0;
        }
        if (visible && dy > 0 || (!visible && dy < 0)) {
            distance += dy;
        }
    }

}

Customize a OnScrollListener, rewrite onScrolled Method . Determine the current scrolling direction 、 Rolling distance 、 Current FloatingActionButton Whether to display for corresponding logic processing .

among HideScrollListener Is a custom listening interface :

public interface HideScrollListener {
    void onHide();
    void onShow();
}

from Activity Implement this interface :

public class FabAnimActivity extends AppCompatActivity implements HideScrollListener {

    private RecyclerView recyclerview;
    private ImageButton fab;
    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Omit some code 

        // Add slide monitor 
        recyclerview.addOnScrollListener(new FabScrollListener(this));
    }

    @Override
    public void onHide() {
        //  Hide animation -- Attribute animation 
        toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
        RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();

        fab.animate().translationY(fab.getHeight() + layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
    }

    @Override
    public void onShow() {
        //  Show animation -- Attribute animation 
        toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));

        fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
    }
}

programme 2: Customize FloatingActionButton Of Behavior Realization Inherit FloatingActionButton Of Behavior:

public class FabBehavior extends FloatingActionButton.Behavior {
    private boolean visible = true;// Whether or not visible 

    // Instantiation CoordinatorLayout.LayoutParams Reflection generation Behavior example , That's why customizing behavior You need to rewrite the following constructor 
    public FabBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
        //  When observing View(RecyclerView) It is called back at the beginning of sliding 
        //nestedScrollAxes: Slide the associated axis ,  We are only concerned with vertical sliding .
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        //  When observing view It is called back when sliding 
        // Perform animation as appropriate 
        if (dyConsumed > 0 && visible) {
            //show
            visible = false;
            onHide(child);
        } else if (dyConsumed < 0) {
            //hide
            visible = true;
            onShow(child);
        }

    }

    public void onHide(FloatingActionButton fab) {
        //  Hide animation -- Attribute animation 
//        toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));

        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
//        fab.animate().translationY(fab.getHeight()+layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
        //FAB  narrow 
        ViewCompat.animate(fab).scaleX(0f).scaleY(0f).start();
    }

    public void onShow(FloatingActionButton fab) {
        //  Show animation -- Attribute animation 
//        toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));

        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
//        fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
        //FAB Zoom in 
        ViewCompat.animate(fab).scaleX(1f).scaleY(1f).start();
    }

}

Constructor must override , rewrite onStartNestedScroll Return to determine which direction of sliding , rewrite onNestedScroll Carry out corresponding logical processing (FloatingActionButton Show and hide the attribute animation of ).

Finally, use... In the layout file CoordinatorLayout Layout , And give FloatingActionButton Add custom Behavior:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="?attr/actionBarSize"
        />

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:title="Fab Animation "
        app:titleTextColor="#fff"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="58dp"
        android:layout_height="58dp"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_favorite_outline_white_24dp"
        app:layout_behavior="com.nan.advancedui.fab.anim.behavior.FabBehavior"
        />

</android.support.design.widget.CoordinatorLayout>

CoordinatorLayout

CoordinatorLayout Is an inheritance from ViewGroup The layout of the container .CoordinatorLayout Listen for sliding child controls to slide through Behavior Feed back to other child controls and perform some animation . Simply speaking , It is to achieve touch by coordinating and scheduling the child controls or layouts inside ( It generally refers to sliding ) Produce some related animation effects . among ,view Of Behavior It is a bridge of communication , We can set view Of Behavior To achieve touch animation scheduling .

Be careful : Sliding controls refer to :RecyclerView/NestedScrollView/ViewPager, signify ListView、ScrollView no way .

Please refer to 《CoordinatorLayout Use full resolution 》

MaterialDesign Animation

1.Touch Feedback( Touch feedback ) 5.0+ Your mobile phone comes with you .

By setting background The attribute value of :

<Button
    android:id="@+id/btn_test"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/selectableItemBackground"
    android:background="?attr/selectableItemBackgroundBorderless"
    android:text=" test "/>

among ,selectableItemBackground It is the water ripple effect with boundary ,selectableItemBackgroundBorderless There is no boundary of the water ripple effect .

You can change the background color and the color of the water ripple , And it's better to use AppcompatActivity:

 <item name="colorControlHighlight">@color/colorPrimary_pink</item>
 <item name="colorButtonNormal">@color/material_blue_grey_800</item>

If you want to change the color of individual controls , It can be realized by nesting a layer of layout outside . 2.Reveal Effect( Disclosure effect ) Example :Activity The effects of the revelation of . The main use of ViewAnimationUtil Tool class implementation :

// Circular water ripple exposing effect 
ViewAnimationUtils.createCircularReveal(
        view, // What's the role of View above 
        centerX, centerY, // The center of diffusion 
        startRadius, // Start spreading initial radius 
        endRadius)// Diffusion end radius 

among , The radius of diffusion is calculated by Pythagorean theorem , for example :

(float) Math.hypot(view.getWidth() / 2, view.getHeight() / 2)
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    view_root = (LinearLayoutCompat) findViewById(R.id.llc_test);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Animator animator = ViewAnimationUtils.createCircularReveal(view_root, view_root.getWidth() / 2, view_root.getHeight() / 2, 0f, (float) Math.hypot(view_root.getWidth() / 2, view_root.getHeight() / 2));
        animator.setDuration(1000);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.start();
    }
}

Because animation is attached to window above , And in the Activity onCreate Method Window Not yet initialized , So you need to onWindowFocusChanged Execute animation in .

3.Activity transition(Activity Transition animation effect ) Two Activity When doing a jump , Transition animation . We used to pass overridePendingTransition Method realization .

The main use of ActivityOptions class . Only support API21 Version above . Version judgment is troublesome , Google is very considerate Designed a compatible class :ActivityOptionsCompat(v4 In bag ), But this class has no transition animation effect in the lower version , It just solves the problem that we judge the version manually .

Use convert animation prerequisites : You need to give two Activity Are set as follows , Let it allow transition animation .

// Method 1 :
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// Method 2 :
 Modify the theme :<item name="android:windowContentTransitions">true</item>

Transition animation can be divided into two categories : Shared element transformation and common transformation . 1) Shared element transformation

Single element :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(MDAnimActivity.this, iv_test, "test");
    Intent intent = new Intent(MDAnimActivity.this, MDAnimSceneTransitionActivity.class);
    startActivity(intent, options.toBundle());
}

Multiple elements are converted at the same time :

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
        .makeSceneTransitionAnimation(this, Pair.create((View)iv1, "iv1"),Pair.create((View)bt, "bt"));
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());

When the page returns, the system automatically realizes , Please have a look at FragmentActivity Of onBackPressed Method :

@Override
public void onBackPressed() {
    if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
        super.onBackPressed();
    }
}

2. Conversion of unshared elements Only API 21 Only then has the following self-contained effect , Therefore, you need to judge the version number when using .

The three systems are equipped with : Sliding effect (Slide)、 Unfold the effect Explode、 Gradient shows hidden effects Fade. Let's say Fade Introduce... As an example :

// Two better Activity Set some , The effect will be better 
Fade fade = new Fade();
fade.setDuration(1000);
getWindow().setExitTransition(fade);// Out of the animation 
getWindow().setEnterTransition(fade);// The animation coming in 


// If there are shared elements , You can set up shared elements , Then it will follow the shared element animation , Other children view Will follow Fade Animation execution .
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());
原网站

版权声明
本文为[Lao Ma's programming journey]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/173/202206221318269029.html