English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
プロジェクトには下拉リフレッシュ機能が必要でしたが、この View は ListView などのコントロールではありません。ViewGroup がこの機能を実現する必要がありました。最初はネットで少し調べましたが、特に適切なものを見つけることができず、コードもよく理解できませんでしたので、自分で書くことにしました。
それで XlistView のソースコードを一つ一つ見て、XLisviewのソースコードを大まかに理解した後、自分で手を付けることに決めました
面倒くさがって、headViewには XListView の HeadView を使いました。とても面倒が省けました:)
下拉リフレッシュ、下拉リフレッシュ、まずは下拉機能を実現することを目指していました。最初は ScrollView を継承して実現しようと考えていましたが、実際には2つの理由で断念しました:
1、ScrollView下には子Viewが1つしかありません。ScrollView下にViewGroupを追加し、headViewを動的に前のViewGroupに追加することもできますが、私はStudioのビジュアルプレビューが直感的だと思っています。
2、ScrollView内にListViewを内嵌すると、衝突が発生します。ListViewをオーバーライドする必要があります。そのため、別のアプローチに移行することにしました!
上記の理由について1:headViewをScrollViewの中GroupViewに動的に追加します。ScrollViewのonViewAdded()メソッドをオーバーライドして、初期解析したheadViewを子GroupViewに追加することができます
@Override public void onViewAdded(View child) { super.onViewAdded(child); //headViewは一番上に配置される必要があるので、まず思いつくのはVerticalのLinearLayoutです LinearLayout linearLayout = (LinearLayout) getChildAt(0); linearLayout.addView(view, 0); }
別のアプローチで、extends LinearLayoutを使用してみましょう!
準備をしておきます。HeaderViewが必要で、HeaderViewの高さと初期時のLayoutの高さを取得する必要があります
private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); }
mHeaderView = new SRefreshHeader(context);
HeaderViewを構造方法でインスタンス化します
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
これはheaderViewの内容エリアiewを解析するもので、少し後にこのviewの高さを取得するために使用します。なぜ上のmHeaderViewを使用しないのかと聞かれるかもしれませんが、コンストラクタに飛び込んで以下のコードを見ることができます
// 初期状態では、下拉刷新viewの高さを0に設定 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); w(mContainer, lp);
mHeaderViewの高さを直接取得すると、それはもちろん0です
getHeaderViewHeight();
getViewHeight();
HeaderViewの高さとLayoutの初期高さを取得する
/** * headViewの高さを取得します */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * SRefreshLayoutの現在のインスタンスの高さを取得します */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); }
準備が完了しました。次に下拉操作を行わなければなりません
ここまで来たら、onTouchEvent()メソッドが思い浮かぶでしょう、確かに!ここから始めましょう
実行下拉すると、3つのプロセスを経験します
ACTION_UP→ACTION_MOVE→ACTION_UP
ACTION_UPイベント、つまり指が押されたときには、私たちが行うのはタッチしたときの座標を記録することだけです
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break;
次にACTION_MOVEイベントがあります。これは最も重要な部分で、HeadViewとLayoutの高さが変更されるのはここです
case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY(); - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//一定の比率で縮小された移動距離 updateHeight(); break;
updateHeaderViewHeightとupdateHeightはHeaderViewの高さとLayoutの高さを変更するものです
private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //現在のレイアウトインスタンスの高さをヘッダービューの高さに加えて最初のレイアウトの高さに更新します //リフレッシュを更新しないとレイアウトの高さが圧縮され、比率を保てない場合 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //もしリフレッシュ中でない場合および高さ if (mHeaderView.getVisiableHeight() < mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); }
Headerの高さを更新する際には、下拉距離に基づいてリフレッシュ距離に達したかどうかを判断します。上記のコードでは、mHeaderViewの初期高さの2倍に達した場合に「リリースしてリフレッシュ」状態に入り、達していない場合は「下拉リフレッシュ」状態を維持します
HeaderViewの状態は以下のように設定されています3それぞれ
public final static int STATE_NORMAL = 0;//下拉リフレッシュ public final static int STATE_READY = 1;//リフレッシュを解放 public final static int STATE_REFRESHING = 2;//リフレッシュ中
高さを更新する方法はheaderViewとlayoutが同じです。それは元の高さに移動距離を加えて、headerViewまたはlayoutに再割り当てることです
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
最後にACTION_UPイベントがあります。これは指がスクリーンから離れたときのことです。ここでは、headerViewの現在の状態に基づいてheaderViewの最終状態を決定する必要があります!
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
resetHeadViewとresetはheaderViewの高さとlayoutの高さをリセットする方法です
private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } }
実現方法も同じです。状態に基づいて判断し、リフレッシュ中の場合はheaderViewが通常表示され、高さは初期の高さです。NORMAL、つまり「下拉リフレッシュ」状態の場合は、リフレッシュが未発生でリセット時、headerViewは非表示にされ、高さはリセットされます
ここまでで下拉リフレッシュの操作も基本的には完了しましたが、通知を行うための回调インターフェースを追加する必要があります
interface OnRefreshListener { void onRefresh(); }
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
ここまでで基本的には完了しました。効果を試してみましょう。なぜか、ListViewにネストされた場合にこのLayoutが下拉リフレッシュを実行できないのか気づきました。少し考えてみると、イベントディスパッチの問題だと思われます。イベントのインターセプトを処理する必要があります!
イベントインターセプトの処理について、鴻洋大神的viewgroupイベントディスパッチのブログとAndroid-Ultra-Pull-To-Refreshの部分のソースコードを探し、解決策を見つけました:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("listViewが見つかりました"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY(); - mStartY; Logs.v("space:", + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); }
その中で
if (space > 0 && !absListView.canScrollVertically(-1) && Math.abs(absListView.getFirstVisiblePosition()) == 0)
spaceは移動距離を意味しており、canScrollVertically()はListViewが垂直方向にスクロールできるかどうかを判断します。引数が負数の場合は上方向、正数の場合は下方向にスクロールします。最後のものはListViewの最初の見えるitemのpositionです
上記のイベントインターセプト処理を加えることで、最初に述べた要件を満たすViewgroupが完成します!
以下にLayoutのソースコードとHeaderView(直接使用しているXlistViewのHeaderView)のソースコードを貼り付けます
public class SRefreshLayout extends LinearLayout { private SRefreshHeader mHeaderView; private RelativeLayout mHeaderViewContent; private boolean isRefreashing; private float mLastY = -1;//押下開始高さ private int mHeaderViewHeight;//ヘッダービューの内容高さ private int mHeight;//レイアウトの高さ private float mStartY; interface OnRefreshListener { void onRefresh(); } public OnRefreshListener mOnRefreshListener; public SRefreshLayout(Context context) { super(context); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); } /** * headViewの高さを取得します */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * SRefreshLayoutの現在のインスタンスの高さを取得します */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("listViewが見つかりました"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY(); - mStartY; Logs.v("space:", + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) mLastY = ev.getRawY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break; //手指离开屏幕时 case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break; case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY(); - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//一定の比率で縮小された移動距離 updateHeight(); break; } return super.onTouchEvent(ev); } private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } } private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //現在のレイアウトインスタンスの高さをヘッダービューの高さに加えて最初のレイアウトの高さに更新します //リフレッシュを更新しないとレイアウトの高さが圧縮され、比率を保てない場合 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //もしリフレッシュ中でない場合および高さ if (mHeaderView.getVisiableHeight() < mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); } public void stopRefresh() { if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); resetHeadView(SRefreshHeader.STATE_NORMAL); reset(SRefreshHeader.STATE_NORMAL); } } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; } }
public class SRefreshHeader extends LinearLayout { private LinearLayout mContainer; private int mState = STATE_NORMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 500; public final static int STATE_NORMAL = 0;//下拉リフレッシュ public final static int STATE_READY = 1;//リフレッシュを解放 public final static int STATE_REFRESHING = 2;//リフレッシュ中 private ImageView mHeadArrowImage; private TextView mHeadLastRefreashTimeTxt; private TextView mHeadHintTxt; private TextView mHeadLastRefreashTxt; private ProgressBar mRefreshingProgress; public SRefreshHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public SRefreshHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // 初期状態では、下拉刷新viewの高さを0に設定 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 表示進度 mHeadArrowImage.clearAnimation(); mHeadArrowImage.setVisibility(View.INVISIBLE); mRefreshingProgress.setVisibility(View.VISIBLE); } else { // 表示矢印画像を表示 mHeadArrowImage.setVisibility(View.VISIBLE); mRefreshingProgress.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { mHeadArrowImage.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mHeadArrowImage.clearAnimation(); } mHeadHintTxt.setText("下拉刷新"); break; case STATE_READY: if (mState != STATE_READY) { mHeadArrowImage.clearAnimation(); mHeadArrowImage.startAnimation(mRotateUpAnim); mHeadHintTxt.setText("リフレッシュをリリース"); } break; case STATE_REFRESHING: mHeadHintTxt.setText("リフレッシュ中"); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getStatus() { return mState; } public int getVisiableHeight() { return mContainer.getHeight(); } }
最後にレイアウトファイルが来ます
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom"> <RelativeLayout android:id="@"+id/slistview_header_content" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:id="@"+id/slistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:id="@"+id/slistview_header_hint_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ドロップダウンリフレッシュ" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp"> <TextView android:id="@"+id/slistview_header_last_refreash_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="前回リフレッシュ時間" android:textSize="12sp" /> <TextView android:id="@"+id/slistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ProgressBar android:id="@"+id/slistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:visibility="invisible" /> <ImageView android:id="@"+id/slistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/slistview_header_progressbar" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:src="@drawable/mmtlistview_arrow" /> </RelativeLayout> </LinearLayout>
これで本文のすべての内容が終わりです。皆様の学習に役立つことを願っています。また、呐喊教程を多くのサポートをお願いします。
声明:本文の内容はインターネットから取得しており、著作権者は所有しておりません。インターネットユーザーが自発的に貢献し、自己でアップロードした内容であり、本サイトは所有権を持ちません。また、人工編集は行われていません。著作権侵害を疑う内容があれば、メールを送信してください:notice#oldtoolbag.com(メールを送信する際には、#を@に置き換えてください。通報を行い、関連する証拠を提供してください。一旦確認が取れましたら、本サイトは即座に侵害を疑われる内容を削除します。)